All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.xmp.path.XMPPathParser Maven / Gradle / Ivy

There is a newer version: 2024.11.18598.20241113T125352Z-241000
Show newest version
// =================================================================================================
// 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;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy