001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.xml.xdm.visitor;
043:
044: import java.util.ArrayList;
045: import java.util.HashSet;
046: import java.util.List;
047: import java.util.Set;
048: import java.util.regex.Matcher;
049: import java.util.regex.Pattern;
050: import javax.xml.XMLConstants;
051: import javax.xml.namespace.QName;
052: import org.netbeans.modules.xml.xdm.XDMModel;
053: import org.netbeans.modules.xml.xdm.nodes.Attribute;
054: import org.netbeans.modules.xml.xdm.nodes.Element;
055: import org.netbeans.modules.xml.xdm.nodes.Node;
056: import org.netbeans.modules.xml.xdm.nodes.NodeImpl;
057: import org.w3c.dom.NamedNodeMap;
058:
059: /**
060: *
061: * @author Nam Nguyen
062: */
063: public class NamespaceRefactorVisitor extends ChildVisitor {
064: private String namespace;
065: private String prefix;
066: private List<Node> path;
067:
068: // keep track of prefixes used by attributes so we avoid removing
069: // their declaration when the new prefix is default prefix.
070: private Set<String> prefixesUsedByAttributesForDefaultNS = new HashSet<String>();
071:
072: private XDMModel model;
073:
074: /**
075: * @deprecated use constructor with QName-valued attributes map. Prefix refactoring
076: * should not without complete map.
077: */
078: public NamespaceRefactorVisitor() {
079: this (null);
080: }
081:
082: public NamespaceRefactorVisitor(XDMModel xdmModel) {
083: model = xdmModel;
084: }
085:
086: public void refactor(NodeImpl tree, String namespace,
087: String newPrefix, List<Node> ancestors) {
088: if (model.getQNameValuedAttributes() == null)
089: return;
090:
091: assert namespace != null : "Cannot refactor null namespace";
092: this .namespace = namespace;
093: prefix = newPrefix;
094: path = ancestors;
095: tree.accept(this );
096: }
097:
098: public void visit(Element e) {
099: path.add(0, e);
100: NamespaceCheck redec = new NamespaceCheck(prefix, namespace, e);
101: if (redec.getPrefixRedeclaration() == null) {
102: visitNode(e);
103:
104: if (namespace.equals(NodeImpl.lookupNamespace(
105: e.getPrefix(), path))) {
106: e.setPrefix(prefix);
107: }
108:
109: for (Attribute sameNamespace : redec
110: .getNamespaceRedeclaration()) {
111: String prefixToRemove = sameNamespace.getLocalName();
112: if (!prefixesUsedByAttributesForDefaultNS
113: .remove(prefixToRemove)) {
114: e.removeAttributeNode(sameNamespace);
115: }
116: }
117:
118: if (redec.getDuplicateDeclaration() != null) {
119: e.removeAttributeNode(redec.getDuplicateDeclaration());
120: }
121: }
122: path.remove(e);
123: }
124:
125: public void visit(Attribute attr) {
126: if (attr.isXmlnsAttribute())
127: return;
128: String attrPrefix = attr.getPrefix();
129:
130: // default namespace is not applicable for attribute, just have no namespaces.
131: if (!isDefaultPrefix(attrPrefix)) {
132: if (namespace.equals(NodeImpl.lookupNamespace(attrPrefix,
133: path))) {
134: if (isDefaultPrefix(prefix)) {
135: prefixesUsedByAttributesForDefaultNS
136: .add(attrPrefix);
137: } else {
138: attr.setPrefix(prefix);
139: }
140: }
141: }
142: if (isQNameValued(attr)) {
143: prefixesUsedByAttributesForDefaultNS
144: .addAll(refactorAttributeValue(attr, namespace,
145: prefix, path, model));
146: }
147: }
148:
149: public static class NamespaceCheck {
150: Attribute duplicate;
151: Attribute prefixRedeclaration;
152: List<Attribute> namespaceRedeclaredAttributes = new ArrayList<Attribute>();
153:
154: public NamespaceCheck(String existingPrefix,
155: String existingNamespace, Element e) {
156: init(existingPrefix, existingNamespace, e);
157: }
158:
159: public Attribute getPrefixRedeclaration() {
160: return prefixRedeclaration;
161: }
162:
163: public List<Attribute> getNamespaceRedeclaration() {
164: return namespaceRedeclaredAttributes;
165: }
166:
167: public Attribute getDuplicateDeclaration() {
168: return duplicate;
169: }
170:
171: private void init(String existingPrefix,
172: String existingNamespace, Element e) {
173: NamedNodeMap nnm = e.getAttributes();
174: for (int i = 0; i < nnm.getLength(); i++) {
175: if (!(nnm.item(i) instanceof Attribute))
176: continue;
177: Attribute attr = (Attribute) nnm.item(i);
178: if (attr.isXmlnsAttribute()) {
179: Attribute samePrefix = null;
180: Attribute sameNamespace = null;
181: String prefix = attr.getLocalName();
182: if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) {
183: prefix = XMLConstants.DEFAULT_NS_PREFIX;
184: }
185: if (prefix.equals(existingPrefix)) {
186: samePrefix = attr;
187: }
188: if (existingNamespace.equals(attr.getValue())) {
189: sameNamespace = attr;
190: }
191: if (samePrefix != null && sameNamespace != null) {
192: duplicate = attr;
193: } else if (samePrefix != null) {
194: prefixRedeclaration = attr;
195: } else if (sameNamespace != null) {
196: namespaceRedeclaredAttributes.add(attr);
197: }
198: }
199: }
200: }
201: }
202:
203: private QName getQName(Element node) {
204: String ns = NodeImpl.lookupNamespace(node.getPrefix(), path);
205: return new QName(ns, node.getLocalName());
206: }
207:
208: private QName getQName(Attribute node) {
209: String p = node.getPrefix();
210: String ns = (p == null || p.length() == 0) ? null : NodeImpl
211: .lookupNamespace(p, path);
212: return new QName(ns, node.getLocalName());
213: }
214:
215: private boolean isQNameValued(Attribute attr) {
216: assert path != null && path.size() > 0;
217: Element e = (Element) path.get(0);
218: QName elementQName = getQName(e);
219: QName attrQName = getQName(attr);
220: List<QName> attrQNames = model.getQNameValuedAttributes().get(
221: elementQName);
222: if (attrQNames != null) {
223: return attrQNames.contains(attrQName);
224: }
225: return false;
226: }
227:
228: public static boolean isDefaultPrefix(String prefix) {
229: return prefix == null
230: || prefix.equals(XMLConstants.DEFAULT_NS_PREFIX);
231: }
232:
233: private static final Pattern p = Pattern.compile("\\s*(\\S+)\\s*");
234:
235: public static List<String> refactorAttributeValue(Attribute attr,
236: String namespace, String prefix, List<Node> context,
237: XDMModel model) {
238: ArrayList<String> prefixesUsedForDefaultNS = new ArrayList<String>();
239: String value = attr.getValue();
240: StringBuilder newValue = null;
241: Matcher m = p.matcher(value);
242: while (m.find()) {
243: String qname = m.group(1);
244: String[] parts = qname.split(":");
245: if (parts.length > 1) {
246: String valuePrefix = parts[0];
247: String valueNamespace = context.size() == 1 ? context
248: .get(0).lookupNamespaceURI(valuePrefix)
249: : NodeImpl
250: .lookupNamespace(valuePrefix, context);
251: if (namespace.equals(valueNamespace)) {
252: if (isDefaultPrefix(prefix)) {
253: prefixesUsedForDefaultNS.add(valuePrefix);
254: } else {
255: if (newValue == null)
256: newValue = new StringBuilder();
257: newValue.append(prefix);
258: newValue.append(":");
259: newValue.append(parts[1]);
260: newValue.append(" ");
261: }
262: }
263: }
264: }
265: if (newValue != null) {
266: attr.setValue(newValue.toString().trim());
267: }
268: return prefixesUsedForDefaultNS;
269: }
270: }
|