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 }