1 package org.codehaus.mojo.jaxb2.schemageneration.postprocessing.schemaenhancement; 2 3 /* 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 22 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.NodeProcessor; 23 import org.w3c.dom.Attr; 24 import org.w3c.dom.Element; 25 import org.w3c.dom.Node; 26 27 import javax.xml.XMLConstants; 28 29 /** 30 * <p><code>NodeProcessor</code> which alters the namespace prefix for all relevant Nodes within an XML 31 * document Node. It alters namespace prefixes in the following logical places:</p> 32 * <dl> 33 * <dt>Schema Namespace Definition</dt> 34 * <dd>xmlns:oldPrefix="http://some/namespace" is altered to xmlns:newPrefix="http://some/namespace"</dd> 35 * <dt>Elements Namespace Prefix</dt> 36 * <dd><oldPrefix:someElement ... > is altered to <newPrefix:someElement ... ></dd> 37 * <dt>Element Reference</dt> 38 * <dd><code><xs:element ref="oldPrefix:aRequiredElementInTheOldPrefixNamespace"/></code> is altered to 39 * <code><xs:element ref="newPrefix:aRequiredElementInTheOldPrefixNamespace"/></code></dd> 40 * <dt>Type Attribute</dt> 41 * <dd><code><xs:element type="oldPrefix:something"/></code> is altered to 42 * <code><xs:element type="newPrefix:something"/></code></dd> 43 * <dt>Type Extension</dt> 44 * <dd><code><xs:extension base="oldPrefix:something"/></code> is altered to 45 * <code><xs:extension base="newPrefix:something"/></code></dd> 46 * </dl> 47 * 48 * @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a> 49 * @since 1.4 50 */ 51 public class ChangeNamespacePrefixProcessor implements NodeProcessor { 52 53 // Constants 54 // <xs:extension base="tns:importItem"> 55 private static final String EXTENSION_ELEMENT_NAME = "extension"; 56 private static final String EXTENSION_BASE_ATTRIBUTE_NAME = "base"; 57 private static final String REFERENCE_ATTRIBUTE_NAME = "ref"; 58 private static final String TYPE_ATTRIBUTE_NAME = "type"; 59 private static final String SCHEMA = "schema"; 60 private static final String XMLNS = "xmlns:"; 61 62 // <xs:element name="someOtherImportItem" type="tns:someOtherImportItem"/> 63 // private static final String ELEMENT_NAME = "element"; 64 65 // Internal state 66 private String oldPrefix; 67 private String newPrefix; 68 69 /** 70 * Creates a new ChangeNamespacePrefixProcessor providing the oldPrefix which should be replaced by the newPrefix. 71 * 72 * @param oldPrefix The old/current namespace prefix 73 * @param newPrefix The new/substituted namespace prefix 74 */ 75 public ChangeNamespacePrefixProcessor(final String oldPrefix, final String newPrefix) { 76 this.oldPrefix = oldPrefix; 77 this.newPrefix = newPrefix; 78 } 79 80 /** 81 * {@inheritDoc} 82 */ 83 public boolean accept(final Node aNode) { 84 85 if (oldPrefix.equals(aNode.getPrefix())) { 86 // Process any nodes on the form [oldPrefix]:something. 87 return true; 88 } 89 90 if (aNode instanceof Attr) { 91 92 // These cases are defined by attribute properties. 93 final Attr attribute = (Attr) aNode; 94 95 if (isNamespaceDefinition(attribute) 96 || isElementReference(attribute) 97 || isTypeAttributeWithPrefix(attribute) 98 || isExtension(attribute)) { 99 return true; 100 } 101 } 102 103 // Nopes. 104 return false; 105 } 106 107 /** 108 * {@inheritDoc} 109 */ 110 public void process(final Node aNode) { 111 112 if (aNode instanceof Attr) { 113 114 final Attr attribute = (Attr) aNode; 115 final Element parentElement = attribute.getOwnerElement(); 116 117 if (isNamespaceDefinition(attribute)) { 118 119 // Use the incredibly smooth DOM way to rename an attribute... 120 parentElement.setAttributeNS(attribute.getNamespaceURI(), XMLNS + newPrefix, aNode.getNodeValue()); 121 parentElement.removeAttribute(XMLNS + oldPrefix); 122 123 } else if (isElementReference(attribute) 124 || isTypeAttributeWithPrefix(attribute) 125 || isExtension(attribute)) { 126 127 // Simply alter the value of the reference 128 final String value = attribute.getValue(); 129 final String elementName = value.substring(value.indexOf(":") + 1); 130 attribute.setValue(newPrefix + ":" + elementName); 131 } 132 } 133 134 if (oldPrefix.equals(aNode.getPrefix())) { 135 // Simply change the prefix to the new one. 136 aNode.setPrefix(newPrefix); 137 } 138 } 139 140 // 141 // Private helpers 142 // 143 144 /** 145 * Discovers if the provided attribute is the oldPrefix namespace definition, i.e. if the given attribute is the 146 * xmlns:[oldPrefix] within the schema Element. 147 * 148 * @param attribute the attribute to test. 149 * @return <code>true</code> if the provided attribute is the oldPrefix namespace definition, i.e. if the given 150 * attribute is the xmlns:[oldPrefix] within the schema Element. 151 */ 152 private boolean isNamespaceDefinition(final Attr attribute) { 153 154 final Element parent = attribute.getOwnerElement(); 155 156 return XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(parent.getNamespaceURI()) 157 && SCHEMA.equalsIgnoreCase(parent.getLocalName()) 158 && oldPrefix.equals(attribute.getLocalName()); 159 } 160 161 /** 162 * Discovers if the provided attribute is a namespace reference to the oldPrefix namespace, on the form 163 * <code><xs:element ref="oldPrefix:anElementInTheOldPrefixNamespace"/></code> 164 * 165 * @param attribute the attribute to test. 166 * @return <code>true</code> if the provided attribute is named "ref" and starts with <code>[oldPrefix]:</code>, in 167 * which case it is a reference to the oldPrefix namespace. 168 */ 169 private boolean isElementReference(final Attr attribute) { 170 return REFERENCE_ATTRIBUTE_NAME.equals(attribute.getName()) 171 && attribute.getValue().startsWith(oldPrefix + ":"); 172 } 173 174 /** 175 * Discovers if the provided attribute is a type attribute using the oldPrefix namespace, on the form 176 * <code><xs:element type="oldPrefix:anElementInTheOldPrefixNamespace"/></code> 177 * 178 * @param attribute the attribute to test. 179 * @return <code>true</code> if the provided attribute is named "type" and starts with <code>[oldPrefix]:</code>, in 180 * which case it is a type in the oldPrefix namespace. 181 */ 182 private boolean isTypeAttributeWithPrefix(final Attr attribute) { 183 return TYPE_ATTRIBUTE_NAME.equals(attribute.getName()) && attribute.getValue().startsWith(oldPrefix + ":"); 184 } 185 186 /** 187 * Discovers if the provided attribute is a namespace reference to the oldPrefix namespace, on the form 188 * <p/> 189 * <pre> 190 * <code><xs:extension base="[oldPrefix]:importItem"></code> 191 * </pre> 192 * 193 * @param attribute the attribute to test. 194 * @return <code>true</code> if the provided attribute is named "extension" and starts with 195 * <code>[oldPrefix]:</code>, in which case it is a reference to the oldPrefix namespace. 196 */ 197 private boolean isExtension(final Attr attribute) { 198 199 final Element parent = attribute.getOwnerElement(); 200 201 return XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(parent.getNamespaceURI()) 202 && EXTENSION_ELEMENT_NAME.equalsIgnoreCase(parent.getLocalName()) 203 && EXTENSION_BASE_ATTRIBUTE_NAME.equalsIgnoreCase(attribute.getName()) 204 && attribute.getValue().startsWith(oldPrefix + ":"); 205 } 206 }