How to use Java String variables inside XPath query - java

I have books.xml file which contains author name and book titles. I am using the following code snippet to query books.xml.
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
XPathExpression expr
= xpath.compile("//book[author= 'Larry Niven']/title/text()");
Now instead of directly putting the name in the query if I want to pass it while the program is running as a String variable how to do it. Just putting the string variable name is not working!

The problem here is when you have an author like the infamous Larry "Basher" O'Niven.
In this case, you will need to escape the variable, as in this naive implementation:
public static String escape(String s) {
Matcher matcher = Pattern.compile("['\"]")
.matcher(s);
StringBuilder buffer = new StringBuilder("concat(");
int start = 0;
while (matcher.find()) {
buffer.append("'")
.append(s.substring(start, matcher.start()))
.append("',");
buffer.append("'".equals(matcher.group()) ? "\"'\"," : "'\"',");
start = matcher.end();
}
if (start == 0) {
return "'" + s + "'";
}
return buffer.append("'")
.append(s.substring(start))
.append("'")
.append(")")
.toString();
}
This can be demonstrated with this code:
String xml =
"<xml><foo bar=\"Larry "Basher" O'Niven\">Ringworm</foo></xml>";
String query =
String.format("//foo[#bar=%s]", escape("Larry \"Basher\" O'Niven"));
System.out.println(query);
String book = XPathFactory.newInstance()
.newXPath()
.evaluate(query, new InputSource(new StringReader(xml)));
System.out.println(query + " > " + book);

You can in fact use both custom functions and variables in XPath -but a quick hack might be more productive for many uses.
Below is some code I developed as a learning tool for our students. It lets you do this:
// create some variable we want to use in the xpath
xPathVariableAndFunctionResolver.newVariable("myNamespace", "id", "xs:string", "l2"); // myNamespace is declared in the namespace context with prefix 'my'
// create an XPath expression
String expression = "//did:Component[#id=$my:id]"; // variable $namespace:name
XPathExpression findComponents = xPathFunctionAndVariableOperator.compile(expression);
// execute the XPath expression against the document
NodeList statements = (NodeList)findComponents.evaluate(document, XPathConstants.NODESET);
And much the same with XPath functions. The code, first a wrapper for the normal XPath evalutation:
public class XPathOperator {
protected XPath xPath;
protected XPathFactory xPathFactory;
private Hashtable<String, XPathExpression> compiled = new Hashtable<String, XPathExpression>();
protected void initFactory() throws XPathFactoryConfigurationException {
xPathFactory = XPathFactory.newInstance(XPathConstants.DOM_OBJECT_MODEL);
}
protected void initXPath(NamespaceContext context) {
xPath = xPathFactory.newXPath();
xPath.setNamespaceContext(context);
}
public XPathOperator(NamespaceContext context) throws XPathFactoryConfigurationException {
initFactory();
initXPath(context);
}
public Object evaluate(Document document, String expression, QName value) throws XPathExpressionException {
// create an XPath expression - http://www.zvon.org/xxl/XPathTutorial/General/examples.html
XPathExpression findStatements = compile(expression);
// execute the XPath expression against the document
return (NodeList)findStatements.evaluate(document, value);
}
public XPathExpression compile(String expression) throws XPathExpressionException {
if(compiled.containsKey(expression)) {
return (XPathExpression) compiled.get(expression);
}
XPathExpression xpath = xPath.compile(expression);
System.out.println("Compiled XPath " + expression);
compiled.put(expression, xpath);
return xpath;
}
}
Then we add the concept of custom variables and functions, of course with namespaces:
public class XPathFunctionAndVariableOperator extends XPathOperator {
public XPathFunctionAndVariableOperator(NamespaceContext context, XPathVariableResolver xPathVariableResolver, XPathFunctionResolver xPathFunctionResolver) throws XPathFactoryConfigurationException {
super(context);
xPath.setXPathVariableResolver(xPathVariableResolver);
xPath.setXPathFunctionResolver(xPathFunctionResolver);
}
}
Which would not be much fun without the variable and function resolvers:
public class XPathVariableAndFunctionResolver implements XPathVariableResolver, XPathFunctionResolver {
private Hashtable functions = new Hashtable();
private Hashtable variables = new Hashtable();
private SchemaDVFactory factory = SchemaDVFactory.getInstance();
public XPathFunction resolveFunction(QName functionName, int arity) {
Hashtable table = (Hashtable)functions.get(functionName.getNamespaceURI());
if(table != null) {
XPathFunction function = (XPathFunction)table.get(functionName.getLocalPart());
if(function == null) {
throw new RuntimeException("Function " + functionName.getLocalPart() + " does not exist in namespace " + functionName.getNamespaceURI() + "!");
}
System.out.println("Resolved function " + functionName + " with " + arity + " argument(s)");
return function;
}
throw new RuntimeException("Function namespace " + functionName.getNamespaceURI() + " does not exist!");
}
/**
*
* Adds a variable using namespace and name, primitive type and default value
*
* #param namespace
* #param name
* #param datatype one of the built-in XML datatypes
* #param value
* #throws InvalidDatatypeValueException if value is not of correct datatype
*/
#SuppressWarnings("unchecked")
public void newVariable(String namespace, String name, String datatype, String value) throws InvalidDatatypeValueException {
int index = datatype.indexOf(":");
if(index != -1) {
datatype = datatype.substring(index+1);
}
XSSimpleType builtInType = factory.getBuiltInType(datatype);
if(builtInType == null) {
throw new RuntimeException("Null type for " + datatype);
}
ValidationState validationState = new ValidationState();
ValidatedInfo validatedInfo = new ValidatedInfo();
builtInType.validate(value, validationState, validatedInfo);
System.out.println("Defined variable " + name + " as " + datatype + " with value " + value);
Hashtable table;
if(!variables.containsKey(namespace)) {
table = new Hashtable();
variables.put(namespace, table);
} else {
table = (Hashtable)variables.get(namespace);
}
table.put(name, new Object[]{validatedInfo, builtInType});
}
public void newVariableValue(String namespace, String name, String value) throws InvalidDatatypeValueException {
ValidationState validationState = new ValidationState();
Hashtable table;
if(!variables.containsKey(namespace)) {
throw new RuntimeException("Unknown variable namespace " + namespace);
} else {
table = (Hashtable)variables.get(namespace);
}
Object[] bundle = (Object[])table.get(name);
ValidatedInfo validatedInfo = (ValidatedInfo)bundle[0];
XSSimpleType builtInType = (XSSimpleType)bundle[1];
builtInType.validate(value, validationState, validatedInfo); // direct reference transfer of value
System.out.println("Assigned value " + validatedInfo.normalizedValue + " to variable " + name);
}
public Object resolveVariable(QName variableName) {
Hashtable table;
if(!variables.containsKey(variableName.getNamespaceURI())) {
throw new RuntimeException("Unknown variable namespace " + variableName.getNamespaceURI());
} else {
table = (Hashtable)variables.get(variableName.getNamespaceURI());
}
Object[] bundle = (Object[])table.get(variableName.getLocalPart());
if(bundle != null) {
ValidatedInfo var = (ValidatedInfo)bundle[0];
if(var != null) {
switch(var.actualValueType) { // some types omitted, customize your own
case XSConstants.INTEGER_DT:
case XSConstants.DECIMAL_DT:
case XSConstants.INT_DT:
case XSConstants.LONG_DT:
case XSConstants.SHORT_DT:
case XSConstants.BYTE_DT:
case XSConstants.UNSIGNEDBYTE_DT:
case XSConstants.UNSIGNEDINT_DT:
case XSConstants.UNSIGNEDLONG_DT:
case XSConstants.UNSIGNEDSHORT_DT:
return new Integer(var.normalizedValue);
case XSConstants.DATE_DT:
case XSConstants.DATETIME_DT:
case XSConstants.GDAY_DT:
case XSConstants.GMONTH_DT:
case XSConstants.GMONTHDAY_DT:
case XSConstants.GYEAR_DT:
case XSConstants.GYEARMONTH_DT:
case XSConstants.DURATION_DT:
case XSConstants.TIME_DT:
return new Date(var.normalizedValue);
case XSConstants.FLOAT_DT:
return new Float(Float.parseFloat(var.normalizedValue));
case XSConstants.DOUBLE_DT:
return new Double(Double.parseDouble(var.normalizedValue));
case XSConstants.STRING_DT:
case XSConstants.QNAME_DT:
return var.normalizedValue;
default:
throw new RuntimeException("Unknown datatype " + var.actualValueType + " for variable " + variableName + " in namespace " + variableName.getNamespaceURI());
}
}
}
throw new RuntimeException("Could not resolve value " + variableName + " in namespace " + variableName.getNamespaceURI());
}
public void addFunction(String namespace, String name, XPathFunction function) {
Hashtable table;
if(!functions.containsKey(namespace)) {
table = new Hashtable();
functions.put(namespace, table);
} else {
table = (Hashtable)functions.get(namespace);
}
table.put(name, function);
}
}
The functions obviously cannot be contained within the above, since typically running custom code (i.e. the whole point is that you write your own class), so go with something like
public abstract class XPathFunctionImpl implements XPathFunction {
/**
* This function is called by the XPath expression as it implements the interface XPathFunction
*/
protected int numberArguments;
public Object evaluate(List args) throws XPathFunctionException {
if(args.size() == numberArguments) {
return evaluateImpl(args);
}
throw new RuntimeException("Illegal number of arguments for " + this);
}
public abstract Object evaluateImpl(List args) throws XPathFunctionException;
}
And then the implement/subclass your own logic in evaluateImpl(..) somehow.
This sure makes the String appending seem quite ... attractive ;) Note: This code is several years old and there might exist a better way of doing all this.

If you want a ready-made implementation you can use commons JXPath which supports declaration of variables:
http://commons.apache.org/jxpath/users-guide.html#Variables

String rawXPath = "//book[author= '" + larrysName + "']/title/text()";
or
String rawXPath = String.format("//book[author= '%s']/title/text()", larrysName);
where larrysName is a variable of type String coming from somewhere.

Related

Initialize multiple numeric fields at once in JAVA that begin with certain values

I am working on a Java class that contains a ton of numeric fields. Most of them would begin with something like 'CMTH' or 'FYTD'. Is it possible to initialize all fields of the same type that begin or end with a certain value. For example I have the following fields:
CMthRepCaseACR CMthRepUnitACR CMthRecCaseACR CMthRecUnitACR CMthHecCaseACR CMthHecUnitACR FYTDHecCaseACR FYTDHecUnitACR CMthBBKCaseACR CMthBBKUnitACR CMthPIHCaseACR .
I am trying to figure if it is possible to initialize all fields to zero that end with an 'ACR' or begin with an 'Cmth"
I know I can do something like cmtha = cmthb = cmthc = 0 but I was wondering there was a command where you can some kind of mask to initialize
Thanks
Assuming that you cannot change that said Java class (and e.g. use a collection or map to store the values) your best bet is probably reflection (see also: Trail: The Reflection API). Reflection gives you access to all fields of the class and you can then implement whatever matching you'd like.
Here's a short demo to get you started, minus error handling, sanity checks and adaptions to your actual class:
import java.util.stream.Stream;
public class Demo {
private static class DemoClass {
private int repCaseACR = 1;
private int CMthRepUnit = 2;
private int foo = 3;
private int bar = 4;
#Override
public String toString() {
return "DemoClass [repCaseACR=" + repCaseACR + ", CMthRepUnit=" + CMthRepUnit + ", foo=" + foo + ", bar="
+ bar + "]";
}
}
public static void main(String[] args) {
DemoClass demoClass = new DemoClass();
System.out.println("before: " + demoClass);
resetFields(demoClass, "CMth", null);
System.out.println("after prefix reset: " + demoClass);
resetFields(demoClass, null, "ACR");
System.out.println("after suffix reset: " + demoClass);
}
private static void resetFields(DemoClass instance, String prefix, String suffix) {
Stream.of(instance.getClass().getDeclaredFields())
.filter(field ->
(prefix != null && field.getName().startsWith(prefix))
|| (suffix != null && field.getName().endsWith(suffix)))
.forEach(field -> {
field.setAccessible(true);
try {
field.set(instance, 0);
} catch (IllegalArgumentException | IllegalAccessException e) {
// TODO handle me
}
});
}
}
Output:
before: DemoClass [repCaseACR=1, CMthRepUnit=2, foo=3, bar=4]
after prefix reset: DemoClass [repCaseACR=1, CMthRepUnit=0, foo=3, bar=4]
after suffix reset: DemoClass [repCaseACR=0, CMthRepUnit=0, foo=3, bar=4]
Note: Both links are seriously dated but the core functionality of reflection is still the same.

Using JsonAnySetter and JsonAnyGetter with ArrayList within Hashmap

So I'm trying to get my head around using Jackson Annotations, and i'm making requests against Riot's API. This is the response I'm getting: http://jsonblob.com/568079c8e4b01190df45d254. Where the array after the summonerId (38584682) can be of a varying length.
The unique summoner ID will be different every single time too.
I want to map this response to a DTO.
For a similar situation with a different call I am doing:
#JsonIgnore
protected Map<String, SingleSummonerBasicDTO> nonMappedAttributes;
#JsonAnyGetter
public Map<String, SingleSummonerBasicDTO> getNonMappedAttributes() {
return nonMappedAttributes;
}
#JsonAnySetter
public void setNonMappedAttributes(String key, SingleSummonerBasicDTO value) {
if (nonMappedAttributes == null) {
nonMappedAttributes = new HashMap<String, SingleSummonerBasicDTO>();
}
if (key != null) {
if (value != null) {
nonMappedAttributes.put(key, value);
} else {
nonMappedAttributes.remove(key);
}
}
}
From an answer on here. my thinking is to do a for-each loop for each of the elements in the array, but I don't know how to loop over something without having something to loop over.
I am completely stuck as to how the annotations work and how to proceed, any help if appreciated!
First of all, #JsonAnySetter was meant to deal with the case of varying properties, not for json arrays of varying length.
Jackson is quite capable of using Java Collections and Maps in serialization and deserialization. You just have to tell it the parameter type of the collection.
In your case, I have used a Map to capture the root element, making it the sole key with a List of DTOs as value. I use Jackson's type system (TypeFactory and JavaType) to tell jackson of all generic types.
This is the DTO that I have used:
public class SingleSummonerBasicDTO
{
public String name;
public String tier;
public String queue;
public List<SingleSummonerBasicDTOEntry> entries;
#Override
public String toString() {
String toString = "\nSingleSummonerBasicDTO: " + name + " " + tier + " " + queue;
for (SingleSummonerBasicDTOEntry entry : entries) {
toString += "\n" + entry.toString();
}
return toString;
}
public static class SingleSummonerBasicDTOEntry
{
public String playerOrTeamId;
public String playerOrTeamName;
public String division;
public int leaguePoints;
public int wins;
public int losses;
public boolean isHotStreak;
public boolean isVeteran;
public boolean isFreshBlood;
public boolean isInactive;
#Override
public String toString() {
return "Entry: " + playerOrTeamId + " " + playerOrTeamName + " " + division + " " + leaguePoints + " " + wins + " " +
losses + " " + isHotStreak + " " + isVeteran + " " + isInactive;
}
}
this is how to deserialise:
public static void main(String[] args)
{
ObjectMapper mapper = new ObjectMapper();
TypeFactory factory = mapper.getTypeFactory();
// type of key of response map
JavaType stringType = factory.constructType(String.class);
// type of value of response map
JavaType listOfDtosType = factory.constructCollectionLikeType(ArrayList.class, SingleSummonerBasicDTO.class);
// create type of map
JavaType responseType = factory.constructMapLikeType(HashMap.class, stringType, listOfDtosType);
try (InputStream is = new FileInputStream("C://Temp/xx.json")) {
Map<String, List<SingleSummonerBasicDTO>> response = new ObjectMapper().readValue(is, responseType);
System.out.println(response);
} catch (IOException e) {
e.printStackTrace();
}
}
output:
{38584682=[
SingleSummonerBasicDTO: Viktor's Masterminds PLATINUM RANKED_SOLO_5x5
Entry: 38584682 Lazrkiller V 64 291 295 false true false,
SingleSummonerBasicDTO: Renekton's Horde SILVER RANKED_TEAM_5x5
Entry: TEAM-ff7d0db0-78ca-11e4-b402-c81f66dba0e7 Y U NO BABAR II 0 4 2 false false false,
SingleSummonerBasicDTO: Pantheon's Chosen SILVER RANKED_TEAM_5x5
Entry: TEAM-d32018f0-d998-11e4-bfd2-c81f66dba0e7 Lose and Throw Away I 66 7 0 false false false,
SingleSummonerBasicDTO: Jayce's Duelists SILVER RANKED_TEAM_5x5
Entry: TEAM-6c8fc440-a8ac-11e4-b65b-c81f66db920c TopBlokesNeverToke III 0 20 18 false false false]}
Here is the way how to parse your json:
Map<String, List<SingleSummonerBasicDTO>> summonersMap = new ObjectMapper()
.readValue(json, new TypeReference<HashMap<String, List<SingleSummonerBasicDTO>>>() {});

How to circumvent Class.forName() method

I need to parse XML file and create bunch of objects from ArrayList with reflection.
I achieved this with Class.forName().
Here is my code:
public ArrayList<Sweets> parse(String fileName) throws Exception {
File file = new File(fileName);
Document doc = builder.parse(file);
ArrayList<Sweets> items = new ArrayList<Sweets>();
int itemCount = Integer.parseInt(path
.evaluate("count(/gift/item)", doc));
for (int i = 1; i <= itemCount; i++) {
double sugar = Double.parseDouble(path.evaluate("/gift/item[" + i + "]/#sugar", doc));
String name = path.evaluate("/gift/item[" + i + "]/name", doc);
double weight = Double.parseDouble(path.evaluate("/gift/item[" + i
+ "]/weight", doc));
Class cl = Class.forName("com.epam.lab.model.chocolate." + name);
items.add((Sweets) cl.getConstructor(double.class, double.class)
.newInstance(sugar, weight));
}
return items;
}
It works but at this line:
Class cl = Class.forName("com.epam.lab.model.chocolate." + name);
But some disadvantage is that this method need to have full name of class include packages.
It takes exactly from distinct package. But I need to take other classes from other location. Limitation of forName() doesn't let me do this.
How to circumvent Class.forName() constraints?
There is a nice alternative to reading and parsing XML into Java objects: JAXB with annotations.
Reading by:
MyDoc doc = load(MyDoc.class, "/mydoc.xml");
private <T> T load(Class<T> klazz, String xmlResource) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(klazz);
return klazz.cast(jaxbContext.createUnmarshaller()
.unmarshal(getClass().getResourceAsStream(xmlResource)));
} catch (JAXBException e) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE,
"Cannot read resource " + xmlResource, e);
throw new IllegalStateException(e);
}
}
#XmlRootElement(name="document")
public class MyDoc {
#XmlElement(name="sweetsList")
public List<Sweets> sweets= new ArrayList<>();
}
<document>
<sweetsList>
...
</sweetsList>
</document>
And same annotations with #XmlElement and #XmlAttribute for the other classes.
I'm afraid it's not possible - to instantiate you need to provide fully qualified name of the class.
But maybe you can provide more information in parsed xml - e.g. part of the package name which differs between types of sweets?
String name = path.evaluate("/gift/item[" + i + "]/name", doc);
String type = path.evaluate("/gift/item[" + i + "]/type", doc);
double weight = Double.parseDouble(path.evaluate("/gift/item[" + i + "]/weight", doc));
Class cl = Class.forName("com.epam.lab.model" + type + "." + name);
Or if XML format is sealed, then maybe you can create a mapping between particular sweets names and packages of classess which represent them?

Refactor parameter names programmatically

Using eclipse's jdt refactoring framework, I am trying to convert two different code bases to the same names. They are almost identical codebases except that names are different.
Function/Field/Class renaming works fine, but when it comes to parameters it yells at me that the workbench is not created yet. However i'm trying to do this in a headless manor.
private void refactor(String task, IJavaElement element, String new_name) throws CoreException
{
RefactoringStatus status = new RefactoringStatus();
RefactoringContribution contrib = RefactoringCore.getRefactoringContribution(task);
RenameJavaElementDescriptor rnDesc = (RenameJavaElementDescriptor)contrib.createDescriptor();
rnDesc.setFlags(JavaRefactoringDescriptor.JAR_MIGRATION | JavaRefactoringDescriptor.JAR_REFACTORING);
rnDesc.setProject(element.getJavaProject().getProject().getName());
rnDesc.setUpdateReferences(true);
rnDesc.setJavaElement(element);
rnDesc.setNewName(new_name);
Refactoring ref = rnDesc.createRefactoring(status);
ref.checkInitialConditions(NULL_MON);
ref.checkFinalConditions(NULL_MON);
Change change = ref.createChange(NULL_MON);
change.perform(NULL_MON);
}
This works fine:
for (IMethod method : type.getMethods())
{
refactor(IJavaRefactorings.RENAME_METHOD, method, {new name});
}
This does not:
for (IMethod method : type.getMethods())
{
for (ILocalVariable param : method.getParameters())
{
refactor(IJavaRefactorings.RENAME_LOCAL_VARIABLE, param, {new name});
}
}
And the error, not really helpful as I said I need to do this in a headless manor {so can't make workbench}
java.lang.IllegalStateException: Workbench has not been created yet.
at org.eclipse.ui.PlatformUI.getWorkbench(PlatformUI.java:92)
at org.eclipse.jdt.internal.ui.javaeditor.ASTProvider.install(ASTProvider.java:245)
at org.eclipse.jdt.internal.ui.javaeditor.ASTProvider.<init>(ASTProvider.java:236)
at org.eclipse.jdt.internal.ui.JavaPlugin.getASTProvider(JavaPlugin.java:710)
at org.eclipse.jdt.ui.SharedASTProvider.getAST(SharedASTProvider.java:128)
at org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser.parseWithASTProvider(RefactoringASTParser.java:119)
at org.eclipse.jdt.internal.corext.refactoring.rename.RenameLocalVariableProcessor.initAST(RenameLocalVariableProcessor.java:231)
at org.eclipse.jdt.internal.corext.refactoring.rename.RenameLocalVariableProcessor.checkInitialConditions(RenameLocalVariableProcessor.java:218)
at org.eclipse.ltk.core.refactoring.participants.ProcessorBasedRefactoring.checkInitialConditions(ProcessorBasedRefactoring.java:203)
UPDATE: Made some progress, now I can refactor functions that are not overrides. But any function that overrides another or an interface screws up:
F_ARGUMENTS = JavaRefactoringDescriptor.class.getDeclaredField("fArguments");
F_ARGUMENTS.setAccessible(true);
private void refactor(IMethod method, String[] names) throws CoreException
{
/* My attempt to fix the interface issues, causes duplicate functions instead of renaming the parameters
IMethod parent = null;
if (method.getDeclaringType().isInterface())
{
parent = MethodChecks.overridesAnotherMethod(method, method.getDeclaringType().newSupertypeHierarchy(NULL_MON));
}
else if (MethodChecks.isVirtual(method))
{
ITypeHierarchy hierarchy = method.getDeclaringType().newTypeHierarchy(NULL_MON);
parent = MethodChecks.isDeclaredInInterface(method, hierarchy, NULL_MON);
if (parent == null)
{
parent = MethodChecks.overridesAnotherMethod(method, hierarchy);
}
}
parent = (parent == null ? method : parent);
if (!method.equals(parent))
{
refactor(parent, names);
return;
}*/
String task = IJavaRefactorings.CHANGE_METHOD_SIGNATURE;
RefactoringStatus status = new RefactoringStatus();
ChangeMethodSignatureRefactoringContribution contrib = (ChangeMethodSignatureRefactoringContribution)RefactoringCore.getRefactoringContribution(task);
ChangeMethodSignatureDescriptor desc = (ChangeMethodSignatureDescriptor)contrib.createDescriptor();
desc.setFlags(JavaRefactoringDescriptor.JAR_MIGRATION |
JavaRefactoringDescriptor.JAR_REFACTORING |
RefactoringDescriptor.MULTI_CHANGE |
RefactoringDescriptor.STRUCTURAL_CHANGE);
Map<String, String> args = null;
try
{
args = (Map<String, String>)F_ARGUMENTS.get(desc);
}
catch (Exception e)
{
e.printStackTrace();
}
String project = method.getJavaProject().getProject().getName();
desc.setProject(method.getJavaProject().getProject().getName());
args.put("input", JavaRefactoringDescriptorUtil.elementToHandle(project, method));
args.put("name", method.getElementName());
args.put("deprecate", "false");
args.put("delegate", "true");
boolean changed = false;
int x = 0;
for (ILocalVariable param : method.getParameters())
{
if (!param.getElementName().equals(names[x]))
{
changed = true;
}
String type = "String"; //Doesn't seem to actually matter as long as they are both the same
String info = type + " " + param.getElementName() + " " + x + " " +
type + " " + names[x] + " false";
args.put("parameter" + (x + 1), info);
x++;
}
if (changed)
{
refactor(desc.createRefactoring(status));
}
}
This is what I came up with:
ChangeSignatureProcessor changeSignatureProcessor = new ChangeSignatureProcessor((IMethod) node.resolveBinding().getJavaElement());
ParameterInfo info=new ParameterInfo("FormContext", "formContext", ParameterInfo.INDEX_FOR_ADDED);
info.setDefaultValue("formContext");
changeSignatureProcessor.getParameterInfos().add(0,info);
RefactoringStatus status = new RefactoringStatus();
CheckConditionsContext context= new CheckConditionsContext();
context.add(new ValidateEditChecker(null));
context.add(new ResourceChangeChecker());
changeSignatureProcessor.checkInitialConditions(monitor);
changeSignatureProcessor.checkFinalConditions(monitor,context);
changeSignatureProcessor.createChange(monitor).perform(monitor);

In Java, how do I parse an xml schema (xsd) to learn what's valid at a given element?

I'd like to be able to read in an XML schema (i.e. xsd) and from that know what are valid attributes, child elements, values as I walk through it.
For example, let's say I have an xsd that this xml will validate against:
<root>
<element-a type="something">
<element-b>blah</element-b>
<element-c>blahblah</element-c>
</element-a>
</root>
I've tinkered with several libraries and I can confidently get <root> as the root element. Beyond that I'm lost.
Given an element I need to know what child elements are required or allowed, attributes, facets, choices, etc. Using the above example I'd want to know that element-a has an attribute type and may have children element-b and element-c...or must have children element-b and element-c...or must have one of each...you get the picture I hope.
I've looked at numerous libraries such as XSOM, Eclipse XSD, Apache XmlSchema and found they're all short on good sample code. My search of the Internet has also been unsuccessful.
Does anyone know of a good example or even a book that demonstrates how to go through an XML schema and find out what would be valid options at a given point in a validated XML document?
clarification
I'm not looking to validate a document, rather I'd like to know the options at a given point to assist in creating or editing a document. If I know "I am here" in a document, I'd like to determing what I can do at that point. "Insert one of element A, B, or C" or "attach attribute 'description'".
This is a good question. Although, it is old, I did not find an acceptable answer. The thing is that the existing libraries I am aware of (XSOM, Apache XmlSchema) are designed as object models. The implementors did not have the intention to provide any utility methods — you should consider implement them yourself using the provided object model.
Let's see how querying context-specific elements can be done by the means of Apache XmlSchema.
You can use their tutorial as a starting point. In addition, Apache CFX framework provides the XmlSchemaUtils class with lots of handy code examples.
First of all, read the XmlSchemaCollection as illustrated by the library's tutorial:
XmlSchemaCollection xmlSchemaCollection = new XmlSchemaCollection();
xmlSchemaCollection.read(inputSource, new ValidationEventHandler());
Now, XML Schema defines two kinds of data types:
Simple types
Complex types
Simple types are represented by the XmlSchemaSimpleType class. Handling them is easy. Read the documentation: https://ws.apache.org/commons/XmlSchema/apidocs/org/apache/ws/commons/schema/XmlSchemaSimpleType.html. But let's see how to handle complex types. Let's start with a simple method:
#Override
public List<QName> getChildElementNames(QName parentElementName) {
XmlSchemaElement element = xmlSchemaCollection.getElementByQName(parentElementName);
XmlSchemaType type = element != null ? element.getSchemaType() : null;
List<QName> result = new LinkedList<>();
if (type instanceof XmlSchemaComplexType) {
addElementNames(result, (XmlSchemaComplexType) type);
}
return result;
}
XmlSchemaComplexType may stand for both real type and for the extension element. Please see the public static QName getBaseType(XmlSchemaComplexType type) method of the XmlSchemaUtils class.
private void addElementNames(List<QName> result, XmlSchemaComplexType type) {
XmlSchemaComplexType baseType = getBaseType(type);
XmlSchemaParticle particle = baseType != null ? baseType.getParticle() : type.getParticle();
addElementNames(result, particle);
}
When you handle XmlSchemaParticle, consider that it can have multiple implementations. See: https://ws.apache.org/commons/XmlSchema/apidocs/org/apache/ws/commons/schema/XmlSchemaParticle.html
private void addElementNames(List<QName> result, XmlSchemaParticle particle) {
if (particle instanceof XmlSchemaAny) {
} else if (particle instanceof XmlSchemaElement) {
} else if (particle instanceof XmlSchemaGroupBase) {
} else if (particle instanceof XmlSchemaGroupRef) {
}
}
The other thing to bear in mind is that elements can be either abstract or concrete. Again, the JavaDocs are the best guidance.
Many of the solutions for validating XML in java use the JAXB API. There's an extensive tutorial available here. The basic recipe for doing what you're looking for with JAXB is as follows:
Obtain or create the XML schema to validate against.
Generate Java classes to bind the XML to using xjc, the JAXB compiler.
Write java code to:
Open the XML content as an input stream.
Create a JAXBContext and Unmarshaller
Pass the input stream to the Unmarshaller's unmarshal method.
The parts of the tutorial you can read for this are:
Hello, world
Unmarshalling XML
I see you have tried Eclipse XSD. Have you tried Eclipse Modeling Framework (EMF)? You can:
Generating an EMF Model using XML Schema (XSD)
Create a dynamic instance from your metamodel (3.1 With the dynamic instance creation tool)
This is for exploring the xsd. You can create the dynamic instance of the root element then you can right click the element and create child element. There you will see what the possible children element and so on.
As for saving the created EMF model to an xml complied xsd: I have to look it up. I think you can use JAXB for that (How to use EMF to read XML file?).
Some refs:
EMF: Eclipse Modeling Framework, 2nd Edition (written by creators)
Eclipse Modeling Framework (EMF)
Discover the Eclipse Modeling Framework (EMF) and Its Dynamic Capabilities
Creating Dynamic EMF Models From XSDs and Loading its Instances From XML as SDOs
This is a fairly complete sample on how to parse an XSD using XSOM:
import java.io.File;
import java.util.Iterator;
import java.util.Vector;
import org.xml.sax.ErrorHandler;
import com.sun.xml.xsom.XSComplexType;
import com.sun.xml.xsom.XSElementDecl;
import com.sun.xml.xsom.XSFacet;
import com.sun.xml.xsom.XSModelGroup;
import com.sun.xml.xsom.XSModelGroupDecl;
import com.sun.xml.xsom.XSParticle;
import com.sun.xml.xsom.XSRestrictionSimpleType;
import com.sun.xml.xsom.XSSchema;
import com.sun.xml.xsom.XSSchemaSet;
import com.sun.xml.xsom.XSSimpleType;
import com.sun.xml.xsom.XSTerm;
import com.sun.xml.xsom.impl.Const;
import com.sun.xml.xsom.parser.XSOMParser;
import com.sun.xml.xsom.util.DomAnnotationParserFactory;
public class XSOMNavigator
{
public static class SimpleTypeRestriction
{
public String[] enumeration = null;
public String maxValue = null;
public String minValue = null;
public String length = null;
public String maxLength = null;
public String minLength = null;
public String[] pattern = null;
public String totalDigits = null;
public String fractionDigits = null;
public String whiteSpace = null;
public String toString()
{
String enumValues = "";
if (enumeration != null)
{
for(String val : enumeration)
{
enumValues += val + ", ";
}
enumValues = enumValues.substring(0, enumValues.lastIndexOf(','));
}
String patternValues = "";
if (pattern != null)
{
for(String val : pattern)
{
patternValues += "(" + val + ")|";
}
patternValues = patternValues.substring(0, patternValues.lastIndexOf('|'));
}
String retval = "";
retval += minValue == null ? "" : "[MinValue = " + minValue + "]\t";
retval += maxValue == null ? "" : "[MaxValue = " + maxValue + "]\t";
retval += minLength == null ? "" : "[MinLength = " + minLength + "]\t";
retval += maxLength == null ? "" : "[MaxLength = " + maxLength + "]\t";
retval += pattern == null ? "" : "[Pattern(s) = " + patternValues + "]\t";
retval += totalDigits == null ? "" : "[TotalDigits = " + totalDigits + "]\t";
retval += fractionDigits == null ? "" : "[FractionDigits = " + fractionDigits + "]\t";
retval += whiteSpace == null ? "" : "[WhiteSpace = " + whiteSpace + "]\t";
retval += length == null ? "" : "[Length = " + length + "]\t";
retval += enumeration == null ? "" : "[Enumeration Values = " + enumValues + "]\t";
return retval;
}
}
private static void initRestrictions(XSSimpleType xsSimpleType, SimpleTypeRestriction simpleTypeRestriction)
{
XSRestrictionSimpleType restriction = xsSimpleType.asRestriction();
if (restriction != null)
{
Vector<String> enumeration = new Vector<String>();
Vector<String> pattern = new Vector<String>();
for (XSFacet facet : restriction.getDeclaredFacets())
{
if (facet.getName().equals(XSFacet.FACET_ENUMERATION))
{
enumeration.add(facet.getValue().value);
}
if (facet.getName().equals(XSFacet.FACET_MAXINCLUSIVE))
{
simpleTypeRestriction.maxValue = facet.getValue().value;
}
if (facet.getName().equals(XSFacet.FACET_MININCLUSIVE))
{
simpleTypeRestriction.minValue = facet.getValue().value;
}
if (facet.getName().equals(XSFacet.FACET_MAXEXCLUSIVE))
{
simpleTypeRestriction.maxValue = String.valueOf(Integer.parseInt(facet.getValue().value) - 1);
}
if (facet.getName().equals(XSFacet.FACET_MINEXCLUSIVE))
{
simpleTypeRestriction.minValue = String.valueOf(Integer.parseInt(facet.getValue().value) + 1);
}
if (facet.getName().equals(XSFacet.FACET_LENGTH))
{
simpleTypeRestriction.length = facet.getValue().value;
}
if (facet.getName().equals(XSFacet.FACET_MAXLENGTH))
{
simpleTypeRestriction.maxLength = facet.getValue().value;
}
if (facet.getName().equals(XSFacet.FACET_MINLENGTH))
{
simpleTypeRestriction.minLength = facet.getValue().value;
}
if (facet.getName().equals(XSFacet.FACET_PATTERN))
{
pattern.add(facet.getValue().value);
}
if (facet.getName().equals(XSFacet.FACET_TOTALDIGITS))
{
simpleTypeRestriction.totalDigits = facet.getValue().value;
}
if (facet.getName().equals(XSFacet.FACET_FRACTIONDIGITS))
{
simpleTypeRestriction.fractionDigits = facet.getValue().value;
}
if (facet.getName().equals(XSFacet.FACET_WHITESPACE))
{
simpleTypeRestriction.whiteSpace = facet.getValue().value;
}
}
if (enumeration.size() > 0)
{
simpleTypeRestriction.enumeration = enumeration.toArray(new String[] {});
}
if (pattern.size() > 0)
{
simpleTypeRestriction.pattern = pattern.toArray(new String[] {});
}
}
}
private static void printParticle(XSParticle particle, String occurs, String absPath, String indent)
{
boolean repeats = particle.isRepeated();
occurs = " MinOccurs = " + particle.getMinOccurs() + ", MaxOccurs = " + particle.getMaxOccurs() + ", Repeats = " + Boolean.toString(repeats);
XSTerm term = particle.getTerm();
if (term.isModelGroup())
{
printGroup(term.asModelGroup(), occurs, absPath, indent);
}
else if(term.isModelGroupDecl())
{
printGroupDecl(term.asModelGroupDecl(), occurs, absPath, indent);
}
else if (term.isElementDecl())
{
printElement(term.asElementDecl(), occurs, absPath, indent);
}
}
private static void printGroup(XSModelGroup modelGroup, String occurs, String absPath, String indent)
{
System.out.println(indent + "[Start of Group " + modelGroup.getCompositor() + occurs + "]" );
for (XSParticle particle : modelGroup.getChildren())
{
printParticle(particle, occurs, absPath, indent + "\t");
}
System.out.println(indent + "[End of Group " + modelGroup.getCompositor() + "]");
}
private static void printGroupDecl(XSModelGroupDecl modelGroupDecl, String occurs, String absPath, String indent)
{
System.out.println(indent + "[GroupDecl " + modelGroupDecl.getName() + occurs + "]");
printGroup(modelGroupDecl.getModelGroup(), occurs, absPath, indent);
}
private static void printComplexType(XSComplexType complexType, String occurs, String absPath, String indent)
{
System.out.println();
XSParticle particle = complexType.getContentType().asParticle();
if (particle != null)
{
printParticle(particle, occurs, absPath, indent);
}
}
private static void printSimpleType(XSSimpleType simpleType, String occurs, String absPath, String indent)
{
SimpleTypeRestriction restriction = new SimpleTypeRestriction();
initRestrictions(simpleType, restriction);
System.out.println(restriction.toString());
}
public static void printElement(XSElementDecl element, String occurs, String absPath, String indent)
{
absPath += "/" + element.getName();
String typeName = element.getType().getBaseType().getName();
if(element.getType().isSimpleType() && element.getType().asSimpleType().isPrimitive())
{
// We have a primitive type - So use that instead
typeName = element.getType().asSimpleType().getPrimitiveType().getName();
}
boolean nillable = element.isNillable();
System.out.print(indent + "[Element " + absPath + " " + occurs + "] of type [" + typeName + "]" + (nillable ? " [nillable] " : ""));
if (element.getType().isComplexType())
{
printComplexType(element.getType().asComplexType(), occurs, absPath, indent);
}
else
{
printSimpleType(element.getType().asSimpleType(), occurs, absPath, indent);
}
}
public static void printNameSpace(XSSchema s, String indent)
{
String nameSpace = s.getTargetNamespace();
// We do not want the default XSD namespaces or a namespace with nothing in it
if(nameSpace == null || Const.schemaNamespace.equals(nameSpace) || s.getElementDecls().isEmpty())
{
return;
}
System.out.println("Target namespace: " + nameSpace);
Iterator<XSElementDecl> jtr = s.iterateElementDecls();
while (jtr.hasNext())
{
XSElementDecl e = (XSElementDecl) jtr.next();
String occurs = "";
String absPath = "";
XSOMNavigator.printElement(e, occurs, absPath,indent);
System.out.println();
}
}
public static void xsomNavigate(File xsdFile)
{
ErrorHandler errorHandler = new ErrorReporter(System.err);
XSSchemaSet schemaSet = null;
XSOMParser parser = new XSOMParser();
try
{
parser.setErrorHandler(errorHandler);
parser.setAnnotationParser(new DomAnnotationParserFactory());
parser.parse(xsdFile);
schemaSet = parser.getResult();
}
catch (Exception exp)
{
exp.printStackTrace(System.out);
}
if(schemaSet != null)
{
// iterate each XSSchema object. XSSchema is a per-namespace schema.
Iterator<XSSchema> itr = schemaSet.iterateSchema();
while (itr.hasNext())
{
XSSchema s = (XSSchema) itr.next();
String indent = "";
printNameSpace(s, indent);
}
}
}
public static void printFile(String fileName)
{
File fileToParse = new File(fileName);
if (fileToParse != null && fileToParse.canRead())
{
xsomNavigate(fileToParse);
}
}
}
And for your Error Reporter use:
import java.io.OutputStream;
import java.io.PrintStream;
import java.text.MessageFormat;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
public class ErrorReporter implements ErrorHandler {
private final PrintStream out;
public ErrorReporter( PrintStream o ) { this.out = o; }
public ErrorReporter( OutputStream o ) { this(new PrintStream(o)); }
public void warning(SAXParseException e) throws SAXException {
print("[Warning]",e);
}
public void error(SAXParseException e) throws SAXException {
print("[Error ]",e);
}
public void fatalError(SAXParseException e) throws SAXException {
print("[Fatal ]",e);
}
private void print( String header, SAXParseException e ) {
out.println(header+' '+e.getMessage());
out.println(MessageFormat.format(" line {0} at {1}",
new Object[]{
Integer.toString(e.getLineNumber()),
e.getSystemId()}));
}
}
For your main use:
public class WDXSOMParser
{
public static void main(String[] args)
{
String fileName = null;
if(args != null && args.length > 0 && args[0] != null)
fileName = args[0];
else
fileName = "C:\\xml\\CollectionComments\\CollectionComment1.07.xsd";
//fileName = "C:\\xml\\PropertyListingContractSaleInfo\\PropertyListingContractSaleInfo.xsd";
//fileName = "C:\\xml\\PropertyPreservation\\PropertyPreservation.xsd";
XSOMNavigator.printFile(fileName);
}
}
It's agood bit of work depending on how compex your xsd is but basically.
if you had
<Document>
<Header/>
<Body/>
<Document>
And you wanted to find out where were the alowable children of header you'd (taking account of namespaces)
Xpath would have you look for '/element[name="Document"]/element[name="Header"]'
After that it depends on how much you want to do. You might find it easier to write or find something that loads an xsd into a DOM type structure.
Course you are going to possibly find all sorts of things under that elment in xsd, choice, sequence, any, attributes, complexType, SimpleContent, annotation.
Loads of time consuming fun.
Have a look at this.
How to parse schema using XOM Parser.
Also, here is the project home for XOM

Categories

Resources