/******************************************************************************* * Copyright (c) 2001, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Jens Lukowski/Innoopract - initial renaming/restructuring * *******************************************************************************/ package org.eclipse.wst.xml.core.internal.document; import java.util.Locale; import org.eclipse.wst.xml.core.internal.commentelement.impl.CommentElementRegistry; import org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration; import org.eclipse.wst.xml.core.internal.contentmodel.CMDataType; import org.eclipse.wst.xml.core.internal.provisional.IXMLCharEntity; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode; import org.eclipse.wst.xml.core.internal.provisional.document.ISourceGenerator; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; import org.w3c.dom.Comment; import org.w3c.dom.DocumentType; import org.w3c.dom.Element; import org.w3c.dom.EntityReference; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.ProcessingInstruction; import org.w3c.dom.Text; /** */ public class XMLGeneratorImpl implements ISourceGenerator { private static final String CDATA_CLOSE = "]]>";//$NON-NLS-1$ private static final String CDATA_OPEN = "<![CDATA[";//$NON-NLS-1$ private static final String COMMENT_CLOSE = "-->";//$NON-NLS-1$ private static final String COMMENT_OPEN = "<!--";//$NON-NLS-1$ private static final String DOCTYPE_OPEN = "<!DOCTYPE";//$NON-NLS-1$ private static final String EMPTY_CLOSE = " />";//$NON-NLS-1$ private static final String END_OPEN = "</";//$NON-NLS-1$ private static XMLGeneratorImpl instance = null; private static final String PI_CLOSE = "?>";//$NON-NLS-1$ private static final String PI_OPEN = "<?";//$NON-NLS-1$ private static final String PUBLIC_ID = "PUBLIC";//$NON-NLS-1$ private static final String SSI_PREFIX = "ssi";//$NON-NLS-1$ //private static final String SSI_FEATURE = "SSI";//$NON-NLS-1$ private static final String SSI_TOKEN = "#";//$NON-NLS-1$ private static final String SYSTEM_ID = "SYSTEM";//$NON-NLS-1$ private static final String TAG_CLOSE = ">";//$NON-NLS-1$ /** */ public synchronized static ISourceGenerator getInstance() { if (instance == null) instance = new XMLGeneratorImpl(); return instance; } /** */ //private boolean isCommentTag(XMLElement element) { // if (element == null) return false; // DocumentImpl document = (DocumentImpl)element.getOwnerDocument(); // if (document == null) return false; // DocumentTypeAdapter adapter = document.getDocumentTypeAdapter(); // if (adapter == null) return false; // if (!adapter.hasFeature(SSI_FEATURE)) return false; // String prefix = element.getPrefix(); // return (prefix != null && prefix.equals(SSI_PREFIX)); //} /** * Helper to modify the tag name in sub-classes */ private static void setTagName(Element element, String tagName) { if (element == null || tagName == null) return; ((ElementImpl) element).setTagName(tagName); } /** * XMLModelGenerator constructor */ private XMLGeneratorImpl() { super(); } /** */ public String generateAttrName(Attr attr) { if (attr == null) return null; String attrName = attr.getName(); if (attrName == null) return null; if (attrName.startsWith(JSPTag.TAG_OPEN)) { if (!attrName.endsWith(JSPTag.TAG_CLOSE)) { // close JSP return (attrName + JSPTag.TAG_CLOSE); } } if (((IDOMAttr) attr).isGlobalAttr() && CMNodeUtil.getAttributeDeclaration(attr) != null) { switch (getAttrNameCase(attr)) { case DocumentTypeAdapter.UPPER_CASE : attrName = attrName.toUpperCase(Locale.ENGLISH); break; case DocumentTypeAdapter.LOWER_CASE : attrName = attrName.toLowerCase(Locale.ENGLISH); break; default : // ASIS_CASE break; } } return attrName; } /** */ public String generateAttrValue(Attr attr) { return generateAttrValue(attr, (char) 0); // no quote preference } /** */ public String generateAttrValue(Attr attr, char quote) { if (attr == null) return null; String name = attr.getName(); SourceValidator validator = new SourceValidator(attr); String value = validator.convertSource(((IDOMNode) attr).getValueSource()); if (value == null || value.length() == 0) { if (name != null && name.startsWith(JSPTag.TAG_OPEN)) return null; if (isBooleanAttr(attr)) { if (((AttrImpl) attr).isXMLAttr()) { // generate the name as value value = attr.getName(); } else { // not to generate '=' and value for HTML boolean return null; } } } return generateAttrValue(value, quote); } /** */ public String generateAttrValue(String value, char quote) { // assume the valid is already validated not to include both quotes if (quote == '"') { if ((value != null) && (value.indexOf('"') >= 0)) quote = '\''; // force } else if (quote == '\'') { if ((value != null) && (value.indexOf('\'') >= 0)) quote = '"'; // force } else { // no preference if ((value != null) && (value.indexOf('"') < 0)) quote = '"'; else quote = '\''; } int length = (value == null ? 0 : value.length()); StringBuffer buffer = new StringBuffer(length + 2); buffer.append(quote); if (value != null) buffer.append(value); buffer.append(quote); return buffer.toString(); } /** * generateCDATASection method * * @return java.lang.String * @param comment * org.w3c.dom.CDATASection */ public String generateCDATASection(CDATASection cdata) { if (cdata == null) return null; String data = cdata.getData(); int length = (data != null ? data.length() : 0); StringBuffer buffer = new StringBuffer(length + 16); buffer.append(CDATA_OPEN); if (data != null) buffer.append(data); buffer.append(CDATA_CLOSE); return buffer.toString(); } /** * generateChild method * * @return java.lang.String * @param org.w3c.dom.Node */ public String generateChild(Node parentNode) { if (parentNode == null) return null; if (!parentNode.hasChildNodes()) return null; StringBuffer buffer = new StringBuffer(); for (Node child = parentNode.getFirstChild(); child != null; child = child.getNextSibling()) { String childSource = generateSource(child); if (childSource != null) buffer.append(childSource); } return buffer.toString(); } /** */ public String generateCloseTag(Node node) { if (node == null) return null; switch (node.getNodeType()) { case Node.ELEMENT_NODE : { ElementImpl element = (ElementImpl) node; if (element.isCommentTag()) { if (element.isJSPTag()) return JSPTag.COMMENT_CLOSE; return COMMENT_CLOSE; } if (element.isJSPTag()) return JSPTag.TAG_CLOSE; if (element.isEmptyTag()) return EMPTY_CLOSE; return TAG_CLOSE; } case Node.COMMENT_NODE : { CommentImpl comment = (CommentImpl) node; if (comment.isJSPTag()) return JSPTag.COMMENT_CLOSE; return COMMENT_CLOSE; } case Node.DOCUMENT_TYPE_NODE : return TAG_CLOSE; case Node.PROCESSING_INSTRUCTION_NODE : return PI_CLOSE; case Node.CDATA_SECTION_NODE : return CDATA_CLOSE; default : break; } return null; } /** * generateComment method * * @return java.lang.String * @param comment * org.w3c.dom.Comment */ public String generateComment(Comment comment) { if (comment == null) return null; String data = comment.getData(); int length = (data != null ? data.length() : 0); StringBuffer buffer = new StringBuffer(length + 8); CommentImpl impl = (CommentImpl) comment; if (!impl.isJSPTag()) buffer.append(COMMENT_OPEN); else buffer.append(JSPTag.COMMENT_OPEN); if (data != null) buffer.append(data); if (!impl.isJSPTag()) buffer.append(COMMENT_CLOSE); else buffer.append(JSPTag.COMMENT_CLOSE); return buffer.toString(); } /** * generateDoctype method * * @return java.lang.String * @param docType * org.w3c.dom.DocumentType */ public String generateDoctype(DocumentType docType) { if (docType == null) return null; String name = docType.getName(); int length = (name != null ? name.length() : 0); StringBuffer buffer = new StringBuffer(length + 16); buffer.append(DOCTYPE_OPEN); buffer.append(' '); if (name != null) buffer.append(name); DocumentTypeImpl dt = (DocumentTypeImpl) docType; String publicID = dt.getPublicId(); String systemID = dt.getSystemId(); if (publicID != null) { buffer.append(' '); buffer.append(PUBLIC_ID); buffer.append(' '); buffer.append('"'); buffer.append(publicID); buffer.append('"'); if (systemID != null) { buffer.append(' '); buffer.append('"'); buffer.append(systemID); buffer.append('"'); } } else { if (systemID != null) { buffer.append(' '); buffer.append(SYSTEM_ID); buffer.append(' '); buffer.append('"'); buffer.append(systemID); buffer.append('"'); } } buffer.append('>'); return buffer.toString(); } /** * generateElement method * * @return java.lang.String * @param element * Element */ public String generateElement(Element element) { if (element == null) return null; // if empty tag is preferrable, generate as empty tag ElementImpl impl = (ElementImpl) element; if (impl.preferEmptyTag()) impl.setEmptyTag(true); StringBuffer buffer = new StringBuffer(); String startTag = generateStartTag(element); if (startTag != null) buffer.append(startTag); String child = generateChild(element); if (child != null) buffer.append(child); String endTag = generateEndTag(element); if (endTag != null) buffer.append(endTag); return buffer.toString(); } /** * generateEndTag method * * @return java.lang.String * @param element * org.w3c.dom.Element */ public String generateEndTag(Element element) { if (element == null) return null; ElementImpl impl = (ElementImpl) element; // first check if tag adapter exists TagAdapter adapter = (TagAdapter) impl.getExistingAdapter(TagAdapter.class); if (adapter != null) { String endTag = adapter.getEndTag(impl); if (endTag != null) return endTag; } if (impl.isEmptyTag()) return null; if (!impl.isContainer()) return null; if (impl.isJSPTag()) return JSPTag.TAG_CLOSE; String tagName = generateTagName(element); int length = (tagName != null ? tagName.length() : 0); StringBuffer buffer = new StringBuffer(length + 4); buffer.append(END_OPEN); if (tagName != null) buffer.append(tagName); buffer.append('>'); return buffer.toString(); } /** * generateEntityRef method * * @return java.lang.String * @param entityRef * org.w3c.dom.EntityReference */ public String generateEntityRef(EntityReference entityRef) { if (entityRef == null) return null; String name = entityRef.getNodeName(); int length = (name != null ? name.length() : 0); StringBuffer buffer = new StringBuffer(length + 4); buffer.append('&'); if (name != null) buffer.append(name); buffer.append(';'); return buffer.toString(); } /** * generatePI method * * @return java.lang.String * @param pi * org.w3c.dom.ProcessingInstruction */ public String generatePI(ProcessingInstruction pi) { if (pi == null) return null; String target = pi.getTarget(); String data = pi.getData(); int length = (target != null ? target.length() : 0); if (data != null) length += data.length(); StringBuffer buffer = new StringBuffer(length + 8); buffer.append(PI_OPEN); if (target != null) buffer.append(target); buffer.append(' '); if (data != null) buffer.append(data); buffer.append(PI_CLOSE); return buffer.toString(); } /** * generateSource method * * @return java.lang.String * @param node * org.w3c.dom.Node */ public String generateSource(Node node) { switch (node.getNodeType()) { case Node.ELEMENT_NODE : return generateElement((Element) node); case Node.TEXT_NODE : return generateText((Text) node); case Node.COMMENT_NODE : return generateComment((Comment) node); case Node.DOCUMENT_TYPE_NODE : return generateDoctype((DocumentType) node); case Node.PROCESSING_INSTRUCTION_NODE : return generatePI((ProcessingInstruction) node); case Node.CDATA_SECTION_NODE : return generateCDATASection((CDATASection) node); case Node.ENTITY_REFERENCE_NODE : return generateEntityRef((EntityReference) node); default : // DOCUMENT break; } return generateChild(node); } /** * generateStartTag method * * @return java.lang.String * @param element * Element */ public String generateStartTag(Element element) { if (element == null) return null; ElementImpl impl = (ElementImpl) element; if (impl.isJSPTag()) { // check if JSP content type and JSP Document IDOMDocument document = (IDOMDocument) element.getOwnerDocument(); if (document != null && document.isJSPType()) { if (document.isJSPDocument() && !impl.hasChildNodes()) { impl.setJSPTag(false); } } else { impl.setJSPTag(false); } } if (impl.isCommentTag() && impl.getExistingAdapter(TagAdapter.class) == null) { CommentElementRegistry registry = CommentElementRegistry.getInstance(); registry.setupCommentElement(impl); } // first check if tag adapter exists TagAdapter adapter = (TagAdapter) impl.getExistingAdapter(TagAdapter.class); if (adapter != null) { String startTag = adapter.getStartTag(impl); if (startTag != null) return startTag; } StringBuffer buffer = new StringBuffer(); if (impl.isCommentTag()) { if (impl.isJSPTag()) buffer.append(JSPTag.COMMENT_OPEN); else buffer.append(COMMENT_OPEN); String tagName = generateTagName(element); if (tagName != null) buffer.append(tagName); } else if (impl.isJSPTag()) { buffer.append(JSPTag.TAG_OPEN); String tagName = generateTagName(element); if (tagName != null) buffer.append(tagName); if (impl.isContainer()) return buffer.toString(); // JSP container } else { buffer.append('<'); String tagName = generateTagName(element); if (tagName != null) buffer.append(tagName); } NamedNodeMap attributes = element.getAttributes(); int length = attributes.getLength(); for (int i = 0; i < length; i++) { AttrImpl attr = (AttrImpl) attributes.item(i); if (attr == null) continue; buffer.append(' '); String attrName = generateAttrName(attr); if (attrName != null) buffer.append(attrName); String attrValue = generateAttrValue(attr); if (attrValue != null) { // attr name only for HTML boolean and JSP buffer.append('='); buffer.append(attrValue); } } String closeTag = generateCloseTag(element); if (closeTag != null) buffer.append(closeTag); return buffer.toString(); } /** */ public String generateTagName(Element element) { if (element == null) return null; IDOMElement xe = (IDOMElement) element; String tagName = element.getTagName(); if (tagName == null) return null; if (xe.isJSPTag()) { if (tagName.equals(JSPTag.JSP_EXPRESSION)) return JSPTag.EXPRESSION_TOKEN; if (tagName.equals(JSPTag.JSP_DECLARATION)) return JSPTag.DECLARATION_TOKEN; if (tagName.equals(JSPTag.JSP_DIRECTIVE)) return JSPTag.DIRECTIVE_TOKEN; if (tagName.startsWith(JSPTag.JSP_DIRECTIVE)) { int offset = JSPTag.JSP_DIRECTIVE.length() + 1; // after '.' return (JSPTag.DIRECTIVE_TOKEN + tagName.substring(offset)); } return (xe.isCommentTag()) ? tagName : null; } else if (tagName.startsWith(JSPTag.TAG_OPEN)) { if (!tagName.endsWith(JSPTag.TAG_CLOSE)) { // close JSP return (tagName + JSPTag.TAG_CLOSE); } } else if (xe.isCommentTag()) { String prefix = element.getPrefix(); if (prefix.equals(SSI_PREFIX)) { return (SSI_TOKEN + element.getLocalName()); } } else { if (!xe.isJSPTag() && xe.isGlobalTag() && // global tag CMNodeUtil.getElementDeclaration(xe) != null) { String newName = tagName; switch (getTagNameCase(xe)) { case DocumentTypeAdapter.UPPER_CASE : newName = tagName.toUpperCase(Locale.ENGLISH); break; case DocumentTypeAdapter.LOWER_CASE : newName = tagName.toLowerCase(Locale.ENGLISH); break; } if (newName != tagName) { tagName = newName; setTagName(element, tagName); } } } return tagName; } /** * generateText method * * @return java.lang.String * @param text * org.w3c.dom.Text */ public String generateText(Text text) { if (text == null) return null; TextImpl impl = (TextImpl) text; String source = impl.getTextSource(); if (source != null) return source; return generateTextData(text, impl.getData()); } /** */ public String generateTextData(Text text, String data) { if (data == null) return null; if (text == null) return null; TextImpl impl = (TextImpl) text; if (impl.isJSPContent() || impl.isCDATAContent()) { return new SourceValidator(impl).convertSource(data); } String source = data; // convert special characters to character entities StringBuffer buffer = null; int offset = 0; int length = data.length(); for (int i = 0; i < length; i++) { String name = getCharName(data.charAt(i)); if (name == null) continue; if (buffer == null) buffer = new StringBuffer(length + 8); if (i > offset) buffer.append(data.substring(offset, i)); buffer.append('&'); buffer.append(name); buffer.append(';'); offset = i + 1; } if (buffer != null) { if (length > offset) buffer.append(data.substring(offset)); source = buffer.toString(); } if (source == null || source.length() == 0) return null; return source; } /** */ private int getAttrNameCase(Attr attr) { DocumentImpl document = (DocumentImpl) attr.getOwnerDocument(); if (document == null) return DocumentTypeAdapter.STRICT_CASE; DocumentTypeAdapter adapter = (DocumentTypeAdapter) document.getAdapterFor(DocumentTypeAdapter.class); if (adapter == null) return DocumentTypeAdapter.STRICT_CASE; return adapter.getAttrNameCase(); } /** */ private String getCharName(char c) { switch (c) { case '<' : return IXMLCharEntity.LT_NAME; case '>' : return IXMLCharEntity.GT_NAME; case '&' : return IXMLCharEntity.AMP_NAME; case '"' : return IXMLCharEntity.QUOT_NAME; } return null; } /** */ private int getTagNameCase(Element element) { DocumentImpl document = (DocumentImpl) element.getOwnerDocument(); if (document == null) return DocumentTypeAdapter.STRICT_CASE; DocumentTypeAdapter adapter = (DocumentTypeAdapter) document.getAdapterFor(DocumentTypeAdapter.class); if (adapter == null) return DocumentTypeAdapter.STRICT_CASE; return adapter.getTagNameCase(); } /** */ private boolean isBooleanAttr(Attr attr) { if (attr == null) return false; CMAttributeDeclaration decl = CMNodeUtil.getAttributeDeclaration(attr); if (decl == null) return false; CMDataType type = decl.getAttrType(); if (type == null) return false; String values[] = type.getEnumeratedValues(); if (values == null) return false; return (values.length == 1 && values[0].equals(decl.getAttrName())); } }