Search code examples
vtd-xml

In VTD-XML how to add new attribute into tag with existing attributes?


I'm using VTD-XML to update XML files. In this I am trying to get a flexible way of maintaining attributes on an element. So if my original element is:

<MyElement name="myName" existattr="orig" />

I'd like to be able to update it to this:

<MyElement name="myName" existattr="new" newattr="newValue" />

I'm using a Map to manage the attribute/value pairs in my code and when I update the XML I'm doing something like the following:

private XMLModifier xm = new XMLModifier();
xm.bind(vn);

for (String key : attr.keySet()) {
     int i = vn.getAttrVal(key);

     if (i!=-1) {
         xm.updateToken(i, attr.get(key));
     } else {
         xm.insertAttribute(key+"='"+attr.get(key)+"'");
     }
}
vn = xm.outputAndReparse();

This works for updating existing attributes, however when the attribute doesn't already exist, it hits the insert (insertAttribute) and I get "ModifyException"

com.ximpleware.ModifyException: There can be only one insert per offset
at com.ximpleware.XMLModifier.insertBytesAt(XMLModifier.java:341)
at com.ximpleware.XMLModifier.insertAttribute(XMLModifier.java:1833)

My guess is that as I'm not manipulating the offset directly this might be expected. However I can see no function to insert an an attribute at a position in the element (at end).

My suspicion is that I will need to do it at the "offset" level using something like xm.insertBytesAt(int offset, byte[] content) - as this is an area I have needed to get into yet is there a way to calculate the offset at which I can insert (just before the end of the tag)?

Of course I may be mis-using VTD in some way here - if there is a better way of achieving this then happy to be directed.

Thanks


Solution

  • That's an interesting limitation of the API I hadn't encountered yet. It would be great if vtd-xml-author could elaborate on technical details and why this limitation exists.

    As a solution to your problem, a simple approach would be to accumulate your key-value pairs to be inserted as a String, and then to insert them in a single call after your for loop has terminated.

    I've tested that this works as per your code:

    private XMLModifier xm_ = new XMLModifier();
    xm.bind(vn);
    
    String insertedAttributes = "";
    for (String key : attr.keySet()) {
         int i = vn.getAttrVal(key);
    
         if (i!=-1) {
             xm.updateToken(i, attr.get(key));
         } else {
             // Store the key-values to be inserted as attributes
             insertedAttributes += " " + key + "='" + attr.get(key) + "'";
         }
    }
    if (!insertedAttributes.equals("")) {
       // Insert attributes only once
       xm.insertAttribute(insertedAttributes);
    }
    

    This will also work if you need to update the attributes of multiple elements, simply nest the above code in while(autoPilot.evalXPath() != -1) and be sure to set insertedAttributes = ""; at the end of each while loop.

    Hope this helps.