com.adobe.xmp.path.XMPPathParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
// Copyright 2012 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================
package com.adobe.xmp.path;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This class provides a parser that parses a path string (using prefixes) and a
* prefix-to-namespace mapping to construct a {@link XMPPath} object.
*/
public class XMPPathParser
{
// Pattern to recognize an array index
private static Pattern ARRAY_INDEX_EXPR = Pattern.compile("\\[(\\d*)\\]");
// Pattern to recognize a QName
// TODO This matches more Prefix/Local names than allowed by XML 1.1, should be improved
private static Pattern PROP_EXPR = Pattern.compile("([^@:/\\[\\]]+):([^:/\\[\\]]+)");
// Pattern to recognize a simple qualifier property
private static Pattern QUALIFIER_EXPR = Pattern.compile("@([^:/\\[\\]]+):([^:/\\[\\]]+)");
// Pattern to recognize a qualifier selector (e.g. specific language)
private static Pattern QUALIFIER_SELECTOR_EXPR = Pattern.compile("\\[\\?([^:/\\[\\]]+):([^:/\\[\\]]+)=('[^']+'|\"[^\"]+\")\\]");
/**
* Creates an {@link XMPPath} object by parsing an XMP path String and creates one path segment
* for each detected segment in the path.
* @param path Path string to parse
* @param prefixContract A Map that contains the mapping between Namespaces and their prefixes.
* This must be provided to interpret the path correctly.
* @throws XMPPathParserException If the path has an invalid format or a prefix is found that is not defined in the prefixContract
* @throws IllegalArgumentException If path or prefixContract is null.
*/
public static XMPPath parse( String path, Map prefixContract ) throws XMPPathParserException
{
if ( path == null || prefixContract == null )
{
throw new IllegalArgumentException("Arguments must not be null");
}
if( path.length() == 0 || (path.length() == 1 && path.charAt( 0 ) == '/') )
{
throw new XMPPathParserException( "Path is empty" );
}
XMPPath xmpPath = new XMPPath();
Matcher arrayMatcher = ARRAY_INDEX_EXPR.matcher(path);
Matcher propMatcher = PROP_EXPR.matcher(path);
Matcher qualifierMatcher = QUALIFIER_EXPR.matcher(path);
Matcher qualifierSelectorMatcher = QUALIFIER_SELECTOR_EXPR.matcher(path);
int index = 0;
while (index < path.length())
{
if (path.charAt(index) == '/') // ignore leading /
{
index++;
}
else if (propMatcher.find(index) && propMatcher.start() == index) // Find normal QNames first
{
String prefix = propMatcher.group(1);
if ( ! prefixContract.containsKey( prefix ))
{
throw new XMPPathParserException( "Unknown namespace prefix: " + prefix );
}
xmpPath.add( XMPPathSegment.createPropertySegment( prefixContract.get( prefix ), propMatcher.group(2) ) );
index = propMatcher.end();
}
else if (arrayMatcher.find(index) && arrayMatcher.start() == index) // followed potentially by an array index
{
// There must be at least one property before the index which denotes the namespace
if( index == 0 || path.charAt( index - 1 ) == '/' || xmpPath.size() == 0 )
{
throw new XMPPathParserException( "Array index without a property" );
}
// Array index is optional
int foundIndex = 0;
if( arrayMatcher.groupCount() > 0 )
{
try
{
String indexString = arrayMatcher.group(1);
// For empty strings assume index as 0, and do not try to parse them
if(indexString != null && indexString.length() > 0)
{
foundIndex = Integer.parseInt( arrayMatcher.group(1) );
}
}
catch (NumberFormatException e)
{
throw new XMPPathParserException( "Array index is not an integer value", e );
}
}
xmpPath.add( XMPPathSegment.createArrayIndexSegment( xmpPath.get( xmpPath.size() - 1 ).getNamespace(), foundIndex ));
index = arrayMatcher.end();
}
else if (qualifierMatcher.find(index) && qualifierMatcher.start() == index) // Find qualifier next
{
String prefix = qualifierMatcher.group(1);
if ( ! prefixContract.containsKey( prefix ))
{
throw new XMPPathParserException( "Unknown namespace prefix: " + prefix );
}
xmpPath.add( XMPPathSegment.createQualifierSegment( prefixContract.get( prefix ), qualifierMatcher.group(2) ) );
index = qualifierMatcher.end();
}
else if (qualifierSelectorMatcher.find(index) && qualifierSelectorMatcher.start() == index) // Find qualifier selectors next
{
String prefix = qualifierSelectorMatcher.group(1);
if ( ! prefixContract.containsKey( prefix ))
{
throw new XMPPathParserException( "Unknown namespace prefix: " + prefix );
}
xmpPath.add( XMPPathSegment.createQualifierSelectorSegment( prefixContract.get( prefix ),
qualifierSelectorMatcher.group(2),
qualifierSelectorMatcher.group(3).substring(1,
qualifierSelectorMatcher.group(3).length() - 1)) );
index = qualifierSelectorMatcher.end();
}
else
{
// If there is something that is not a QName it must be just a namespace
String subPath = path.substring( index );
throw new XMPPathParserException( "Invalid Property: " + subPath + ". Only qualified Names are alowed! ");
}
}
return xmpPath;
}
}