/**
* Copyright 2016 StreamSets Inc.
*
* Licensed under 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 com.streamsets.pipeline.lib.xml.xpath;
import com.google.common.base.Strings;
import com.streamsets.pipeline.lib.xml.Constants;
import javax.xml.namespace.QName;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.StartElement;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
class ElementMatcherImpl implements ElementMatcher {
private boolean byIndex = false;
private boolean byAttribute = false;
private int index = 0;
private String attributeName = null;
private String attributeValue = null;
private boolean wildcardElement = false;
private final String elementName;
private int numElementsSeen = 0;
private final String namespacePrefix;
private final Map<String, String> namespaces = new HashMap<>();
private final boolean ignoreNamespaces;
ElementMatcherImpl(String xPathPart, Map<String, String> namespaces, boolean ignoreNamespaces) {
this.ignoreNamespaces = ignoreNamespaces;
final int nsSeparatorIndex = xPathPart.indexOf(Constants.NAMESPACE_PREFIX_SEPARATOR);
String xPathLocalPart = xPathPart;
if (nsSeparatorIndex > 0) {
namespacePrefix = xPathPart.substring(0, nsSeparatorIndex);
xPathLocalPart = xPathPart.substring(nsSeparatorIndex+1);
} else {
namespacePrefix = null;
}
if (namespaces != null) {
this.namespaces.putAll(namespaces);
}
final int qualifierStart = xPathLocalPart.lastIndexOf('[');
if (qualifierStart > 0) {
final int qualifierEnd = xPathLocalPart.lastIndexOf(']');
if (xPathLocalPart.charAt(qualifierStart + 1) == '@') {
byAttribute = true;
final int equalsIndex = xPathLocalPart.lastIndexOf('=');
attributeName = xPathLocalPart.substring(qualifierStart + 2, equalsIndex);
// knock off one character on each end for quotes
attributeValue = xPathLocalPart.substring(equalsIndex + 2, qualifierEnd - 1);
} else {
byIndex = true;
index = Integer.parseInt(xPathLocalPart.substring(qualifierStart + 1, qualifierEnd));
}
elementName = xPathLocalPart.substring(0, qualifierStart);
} else {
elementName = xPathLocalPart;
}
if (Constants.WILDCARD.equals(elementName)) {
wildcardElement = true;
}
}
@Override
public boolean checkStartElement(StartElement startElement) {
if (wildcardElement || isQualifiedMatch(startElement.getName())) {
numElementsSeen++;
if (byIndex) {
return numElementsSeen == index;
} else if (byAttribute) {
final Iterator<?> attrIter = startElement.getAttributes();
while (attrIter.hasNext()) {
Attribute attrib = (Attribute) attrIter.next();
if (attrib.getName().getLocalPart().equals(attributeName) &&
(Constants.WILDCARD.equals(attributeValue)) || attrib.getValue().equals(attributeValue)) {
return true;
}
}
return false;
} else {
return true;
}
} else {
return false;
}
}
private boolean isQualifiedMatch(QName elementQName) {
boolean namespaceMatches;
if (namespacePrefix == null) {
// xpath has no prefix; the element should therefore also have no namespace if namespaces are not ignored
namespaceMatches = ignoreNamespaces || Strings.isNullOrEmpty(elementQName.getNamespaceURI());
} else {
namespaceMatches = namespaces.containsKey(namespacePrefix) &&
namespaces.get(namespacePrefix).equals(elementQName.getNamespaceURI());
}
return elementQName.getLocalPart().equals(this.elementName) && namespaceMatches;
}
}