| /******************************************************************************* |
| * Copyright (c) 2001, 2004 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 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(); |
| break; |
| case DocumentTypeAdapter.LOWER_CASE : |
| attrName = attrName.toLowerCase(); |
| 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(); |
| break; |
| case DocumentTypeAdapter.LOWER_CASE : |
| newName = tagName.toLowerCase(); |
| 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 = document.getDocumentTypeAdapter(); |
| 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 = document.getDocumentTypeAdapter(); |
| 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())); |
| } |
| } |