blob: 49ce625d64d45e1f1cffd9712f4820cf20e49e8e [file] [log] [blame]
/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
package org.apache.jsieve.util;
import java.io.IOException;
/**
* <p>Converts Sieve nodes to xml.
* Settings default to <code>draft-freed-sieve-in-xml-01</code>
* <blockquote cite='http://tools.ietf.org/id/draft-freed-sieve-in-xml-01.txt'>
* Sieve Email Filtering: Sieves and display directives in XML</blockquote>.
* </p>
* <h4>Known limitations</h4>
* <p>For simplicity, allow elements are out into a single namespace.</p>
*/
public class SieveToXml {
public static final String DEFAULT_NAME_ATTRIBUTE = "name";
public static final String DEFAULT_NAME_ACTION_COMMAND = "action";
public static final String DEFAULT_NAME_CONTROL_COMMAND = "control";
public static final String DEFAULT_NAME_TEST = "test";
public static final String DEFAULT_NAME_LIST = "list";
public static final String DEFAULT_NAME_NUM = "num";
public static final String DEFAULT_NAME_TAG = "tag";
public static final String DEFAULT_NAME_STRING = "str";
public static final String DEFAULT_PREFIX = "sieve";
public static final String DEFAULT_NAMESPACE = "urn:ietf:params:xml:ns:sieve";
/** Control commands (as listed in RFC 3028) */
public static final String[] CONTROL_COMMANDS = {"If", "Require", "Stop"};
/**
* <p>Simple infoset output.
* </p><p>
* Note that the approach taken to namespaces is slightly different from both
* <a href='http://java.sun.com/j2ee/1.4/docs/api/javax/xml/namespace/QName.html'>QName</a>
* and <a href='http://www.saxproject.org'>SAX</a>.
* </p>
* <ul>
* <li>
* The <code>localPart</code> parameter gives the
* <a href='http://www.w3.org/TR/REC-xml-names/#NT-LocalPart' rel='tag'>LocalPart</a>
* and <code>MUST</code> be provided in all cases.
* When the name is an <a href='http://www.w3.org/TR/REC-xml-names/#NT-UnprefixedName' rel='tag'>UnprefixedName</a>,
* this is the complete <a href='http://www.w3.org/TR/REC-xml-names/#dt-qualname' rel='tag'>QName</a>.
* When the name is a <a href='http://www.w3.org/TR/REC-xml-names/#NT-PrefixedName' rel='tag'>PrefixedName</a>,
* the output must combine this with the <a href='http://www.w3.org/TR/REC-xml-names/#NT-Prefix' rel='tag'>Prefix</a>
* </li>
* <li>
* </li>
* </ul>
* </p>
*/
public interface Out {
/**
* Starts an XML element.
* @throws IOException when output fails
*/
public void openElement(CharSequence localName, CharSequence uri, CharSequence prefix) throws IOException;
/**
* Outputs a attribute.
* @param value unescaped XML attribute content, not null
* @throws IOException when output fails
*/
public void attribute(CharSequence localName, CharSequence uri, CharSequence prefix, CharSequence value) throws IOException;
/**
* Outputs body text.
* All attribute will be output before any body text
* so this call implicitly marks the end of any attributes
* for the element.
* @param text unescaped body text, not null
* @throws IOException when output fails
*/
public void content(CharSequence text) throws IOException;
/**
* Ends an XML Element.
* @throws IOException when output fails
*/
public void closeElement() throws IOException;
}
/**
* Maps node names to element names.
*/
public interface NameMapper {
/**
* Converts the given node name to an
* element local name.
* @param name not null
* @return element local name to use for given name, not null
*/
public String toElementName(String name);
}
/**
* Creates a mapper which will return the same
* name for any node.
* @param elementLocalName to be returned for all names, not null
* @return not null
*/
public static final NameMapper uniformMapper(final String elementLocalName) {
return new NameMapper() {
public String toElementName(String name) {
return elementLocalName;
}
};
}
/**
* Creates a mapper which returns values given in
* <code>draft-freed-sieve-in-xml-01</code>
* <blockquote cite='http://tools.ietf.org/id/draft-freed-sieve-in-xml-01.txt'>
* Sieve Email Filtering: Sieves and display directives in XML</blockquote>.
* @return not null
*/
public static final NameMapper sieveInXmlMapper() {
return new NameMapper() {
public String toElementName(String name) {
boolean isControlCommand = false;
for (int i=0;i< CONTROL_COMMANDS.length;i++) {
if (CONTROL_COMMANDS[i].equalsIgnoreCase(name)) {
isControlCommand = true;
break;
}
}
final String result;
if (isControlCommand) {
result = DEFAULT_NAME_CONTROL_COMMAND;
} else {
result = DEFAULT_NAME_ACTION_COMMAND;
}
return result;
}
};
}
private String namespaceUri = DEFAULT_NAMESPACE;
private String namespacePrefix = DEFAULT_PREFIX;
private String stringElementName = DEFAULT_NAME_STRING;
private String tagElementName = DEFAULT_NAME_TAG;
private String numberElementName = DEFAULT_NAME_NUM;
private String listElementName = DEFAULT_NAME_LIST;
private String nameAttributeName = DEFAULT_NAME_ATTRIBUTE;
private NameMapper commandNameMapper = sieveInXmlMapper();
private NameMapper testNameMapper = uniformMapper(DEFAULT_NAME_TEST);
/**
* Gets mapper for command names.
* @return not null
*/
public NameMapper getCommandNameMapper() {
return commandNameMapper;
}
/**
* Sets mapper for command names.
* @param commandNameMapper
*/
public void setCommandNameMapper(NameMapper commandNameMapper) {
this.commandNameMapper = commandNameMapper;
}
/**
* Gets the element name used for lists.
* @return element name used for lists, not null
*/
public String getListElementName() {
return listElementName;
}
/**
* Sets the element name used for lists.
* @param listElementName not null
*/
public void setListElementName(String listElementName) {
this.listElementName = listElementName;
}
/**
* Gets the name of the attribute to be used to name command and tests.
* @return name, or null when not attribute should be written
*/
public String getNameAttributeName() {
return nameAttributeName;
}
/**
* Sets the name of the attribute to be used to indicate command and test names.
* @param nameAttributeName naming attribute,
* or null when no attribute should be used
*/
public void setNameAttributeName(String nameAttributeName) {
this.nameAttributeName = nameAttributeName;
}
/**
* Gets the namespace prefix to be used for all elements and attributes.
* @return namespace prefix, or null when no namespace should be used
*/
public String getNamespacePrefix() {
return namespacePrefix;
}
/**
* Sets the namespace prefix to be used for all elements and attributes.
* @param namespacePrefix namespace, or null when no namespace should be used
*/
public void setNamespacePrefix(String namespacePrefix) {
this.namespacePrefix = namespacePrefix;
}
/**
* Gets the namespace URI to be used for all elements and attributes.
* @return namespace URI, or null when no namespace should be used
*/
public String getNamespaceUri() {
return namespaceUri;
}
/**
* Sets the namespace uri to be used for all elements and attributes.
* @param namespaceUri namespace URI, or null when no namespace should be used
*/
public void setNamespaceUri(String namespaceUri) {
this.namespaceUri = namespaceUri;
}
/**
* Gets the name of the element that wraps a numeric argument.
* @return not null
*/
public String getNumberElementName() {
return numberElementName;
}
/**
* Sets the name of the element that wraps a numeric argument.
* @param numberElementName not null
*/
public void setNumberElementName(String numberElementName) {
this.numberElementName = numberElementName;
}
/**
* Gets the name of the element that wraps a string element.
* @return not null
*/
public String getStringElementName() {
return stringElementName;
}
/**
* Sets the name of the element that wraps a string element.
* @param stringElementName not null
*/
public void setStringElementName(String stringElementName) {
this.stringElementName = stringElementName;
}
/**
* Gets the name of the element that wraps a tag element.
* @return not null
*/
public String getTagElementName() {
return tagElementName;
}
/**
* Sets the name of the element that wraps a tag element
* @param tagElementName not null
*/
public void setTagElementName(String tagElementName) {
this.tagElementName = tagElementName;
}
/**
* Gets the mapper for names of test nodes.
* @return not null
*/
public NameMapper getTestNameMapper() {
return testNameMapper;
}
/**
* Sets the mapper for names of test nodes.
* @param testNameMapper not null
*/
public void setTestNameMapper(NameMapper testNameMapper) {
this.testNameMapper = testNameMapper;
}
/**
* Builds a handler to writes to the given output.
* @param out output, not null
* @return hanlder, not null
*/
public SieveHandler build(final Out out) {
final Worker worker = new Worker(nameAttributeName, namespaceUri, namespacePrefix, stringElementName,
tagElementName, numberElementName, listElementName, commandNameMapper, testNameMapper, out);
return worker;
}
/**
* Worker performs actual conversion allowing enclosing to be shared safely
* between threads.
*/
private static final class Worker extends SieveHandler.Base {
private final String nameAttributeName;
private final String namespaceUri;
private final String namespacePrefix;
private final String stringElementName;
private final String tagElementName;
private final String numberElementName;
private final String listElementName;
private final NameMapper commandNameMapper;
private final NameMapper testNameMapper;
private final Out out;
public Worker(final String nameAttributeName, final String namespaceUri, final String namespacePrefix,
final String stringElementName, final String tagElementName, final String numberElementName,
final String listElementName, final NameMapper commandNameMapper, final NameMapper testNameMapper,
final Out out) {
super();
this.nameAttributeName = nameAttributeName;
this.namespaceUri = namespaceUri;
this.namespacePrefix = namespacePrefix;
this.stringElementName = stringElementName;
this.tagElementName = tagElementName;
this.numberElementName = numberElementName;
this.listElementName = listElementName;
this.commandNameMapper = commandNameMapper;
this.testNameMapper = testNameMapper;
this.out = out;
}
@Override
public SieveHandler endCommand(String commandName) throws HaltTraversalException {
return closeElement();
}
private SieveHandler closeElement() throws HaltTraversalException {
try {
out.closeElement();
return this;
} catch (IOException e) {
throw new HaltTraversalException(e);
}
}
@Override
public SieveHandler startCommand(String commandName) throws HaltTraversalException {
try {
out.openElement(commandNameMapper.toElementName(commandName), namespaceUri, namespacePrefix);
nameAttribute(commandName);
return this;
} catch (IOException e) {
throw new HaltTraversalException(e);
}
}
@Override
public SieveHandler listMember(String string) throws HaltTraversalException {
try {
out.openElement(stringElementName, namespaceUri, namespacePrefix);
out.content(string);
out.closeElement();
return this;
} catch (IOException e) {
throw new HaltTraversalException(e);
}
}
@Override
public SieveHandler argument(int number) throws HaltTraversalException {
try {
out.openElement(numberElementName, namespaceUri, namespacePrefix);
out.content(Integer.toString(number));
out.closeElement();
return this;
} catch (IOException e) {
throw new HaltTraversalException(e);
}
}
@Override
public SieveHandler argument(String tag) throws HaltTraversalException {
try {
out.openElement(tagElementName, namespaceUri, namespacePrefix);
out.content(tag);
out.closeElement();
return this;
} catch (IOException e) {
throw new HaltTraversalException(e);
}
}
@Override
public SieveHandler endTest(String testName) throws HaltTraversalException {
return closeElement();
}
@Override
public SieveHandler startTest(String testName) throws HaltTraversalException {
try {
out.openElement(testNameMapper.toElementName(testName), namespaceUri, namespacePrefix);
nameAttribute(testName);
return this;
} catch (IOException e) {
throw new HaltTraversalException(e);
}
}
@Override
public SieveHandler endTestList() throws HaltTraversalException {
return closeElement();
}
@Override
public SieveHandler startTestList() throws HaltTraversalException {
try {
out.openElement(listElementName, namespaceUri, namespacePrefix);
return this;
} catch (IOException e) {
throw new HaltTraversalException(e);
}
}
private void nameAttribute(String name) throws IOException {
if (nameAttributeName != null) {
out.attribute(nameAttributeName, namespaceUri, namespacePrefix, name);
}
}
}
}