/**
* Copyright 2011 meltmedia
*
* Licensed 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.xchain.framework.util;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.JXPathException;
import java.util.List;
import java.util.ArrayList;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.regex.Matcher;
import org.xchain.framework.jxpath.JXPathValidator;
import javax.xml.namespace.NamespaceContext;
/**
* Utility class for Attribute related methods.
*
* @author Christian Trimble
* @author Devon Tackett
* @author Josh Kennedy
*
* @see org.xml.sax.Attributes
*/
public class AttributesUtil
{
private static Logger log = LoggerFactory.getLogger(AttributesUtil.class);
/** A regex that matches the fixed part of an attribute value template. */
public static final String FIXED_PART_REGEX = "((?:[^{}]+|\\{\\{|\\}\\})+)";
/** A regex that matches the dynamic part of an attribute value template. */
public static final String DYNAMIC_PART_REGEX = "(?:\\{((?:[^\\}\'\"]*|\"[^\"]*\"|\'[^\']*\')*)\\})";
/**
* The pattern for attribute value templates. The first group of this pattern is a fixed part, the second group is a dynamic part. This
* pattern will match one part at a time.
*/
private static Pattern attributeValueTemplatePattern = null;
static {
try {
attributeValueTemplatePattern = Pattern.compile(FIXED_PART_REGEX+"|"+DYNAMIC_PART_REGEX);
}
catch( PatternSyntaxException pse ) {
log.error("Could not compile attribute value template pattern.", pse);
}
}
/**
* Get the attribute value.
*
* @param attributes The list of attributes to check.
* @param namespaceUri The namespace of the attribute.
* @param localName The local name of the attribute.
*
* @return The value of the attribute if found. Null if not found.
*/
public static String getAttribute( Attributes attributes, String namespaceUri, String localName )
{
return getAttribute( attributes, namespaceUri, localName, null );
}
/**
* Get the attribute value.
*
* @param attributes The list of attributes to check.
* @param namespaceUri The namespace of the attribute.
* @param localName The local name of the attribute.
* @param defaultValue The default value to use if the attribute could not be found.
*
* @return The value of the attribute if found. The given defaultValue if not found.
*/
public static String getAttribute( Attributes attributes, String namespaceUri, String localName, String defaultValue )
{
String value = defaultValue;
// get the index of the target attribute.
int index = attributes.getIndex( namespaceUri, localName );
// if the index is 0 or greater, then return the value.
if( index >= 0 ) {
value = attributes.getValue( index );
}
return value;
}
/**
* Parses an attribute value template into fixed and dynamic parts. This list will always start with a fixed part and
* then include alternating dynamic and fixed parts.
*/
public static List<String> parseAttributeValueTemplate( String attributeValueTemplate )
throws SAXException
{
// the result.
ArrayList<String> result = new ArrayList<String>();
// create the matcher.
Matcher matcher = attributeValueTemplatePattern.matcher(attributeValueTemplate);
while( matcher.lookingAt() ) {
String fixedPart = matcher.group(1);
String dynamicPart = matcher.group(2);
if( result.isEmpty() && fixedPart == null ) {
result.add("");
}
if( fixedPart != null ) {
result.add(fixedPart.replaceAll("\\{\\{", "{").replaceAll("\\}\\}", "}"));
}
if( dynamicPart != null ) {
result.add(dynamicPart);
}
matcher.region(matcher.regionStart()+matcher.group().length(), matcher.regionEnd());
}
if( !matcher.hitEnd() ) {
throw new SAXException("The attribute value template '"+attributeValueTemplate+"' has an error between characters "+matcher.regionStart()+" and "+matcher.regionEnd()+".");
}
return result;
}
/**
* Evaluates an attribute value template using the provided JXPathContext object.
*/
public static String evaluateAttributeValueTemplate( JXPathContext context, String attributeValueTemplate )
throws SAXException, Exception
{
List<String> parsedAvt = parseAttributeValueTemplate(attributeValueTemplate);
// if there is just one part, then return the string.
if( parsedAvt.size() == 1 ) {
return parsedAvt.get(0);
}
// build a string buffer and start building the result in it.
StringBuilder sb = new StringBuilder();
for( int i = 0; i < parsedAvt.size(); i+=2 ) {
sb.append(parsedAvt.get(i));
if( (i+1) < parsedAvt.size() ) {
sb.append((String)context.getValue(parsedAvt.get(i+1), String.class));
}
}
return sb.toString();
}
public static void validateAttributeValueTemplate( String attributeValueTemplate, NamespaceContext namespaceContext )
throws JXPathException
{
List<String> parsedAvt = null;
try {
parsedAvt = parseAttributeValueTemplate(attributeValueTemplate);
}
catch( SAXException saxe ) {
throw new JXPathException(saxe.getMessage());
}
// validate the xpaths nested in the attribute value template.
for( int i = 1; i < parsedAvt.size(); i+=2 ) {
JXPathValidator.validate( parsedAvt.get(i), namespaceContext );
}
}
}