Re: Parsing a MODS-document with validation fails

Rolf Lear <[email protected]>
Newsgroups gmane.comp.java.jdom.general
Message-ID <[email protected]>
I've put together a test case for this. See attached files. The XML and 
XSD files go in junit-test/resources
The TestSAXComplicatedSchema.java goes in 
junit-test/src/java/org/jdom/tes/cases/input

Whatever fix we decide on can be run through this.... currently it just 
reproduces the problem.

The 'bonus' is that the XML/schema/imports are much simpler than the 
MODS stuff.

Thomas, I've looked at your latest patch, and I think it is too 
heavy-weight... in the sense that it carries a lot of data through the 
hierarchy... two maps, a list, it all seems like too much. I struggled 
to follow some of the logic. I think there's a simpler option.

I fact, when I looked more closely, the data is all available. If you 
encounter an attribute with the same qName and localname, but with a 
URI, then hunt up the Element hierarchy for a prefixed  declaration of 
that namespace.

This method requires no additional member fields, and I believe it 
correctly sets the prefixes.... but, not quite.... there is one case 
where it legitimately may not have an available prefix...

There are conditions where there is no prefix available for a 
default/fixed attribute. Specifically, if an attribute is declared to be 
form="qualified", and the Default namespace for the document is the 
targetNamespace for the attribute.... and, the XML document does not 
have an explicitly prefixed version of the default namespace as well, 
then there is no way to get the namespace prefix. Wow, that was a mouthful.

There has to be some mechanism to create a 'default' or 'computed' prefix.

I have run through a lot of different scenarios, and I figure, the issue 
can be 'easily' solved with a 'simple' patch. The following code will 
reuse the most appropriate prefix, if there is one, and it will create a 
prefix in those conditions where there is not one available. The 
'generated' ones will be of the form attns0 through attns.... and it 
will keep looking until if finds an unused prefix.

As it happens, the necessary code change is very localized in 
SAXHandler. Just replace one line!

This approach localizes any negative (buggy) impacts to just one 
'outside' condition, and does not introduce potential bugs in the 
broader SAXHandler.

of course, there's probably something I have overlooked.... any ideas?

IN SAXHandler, you can add a new 'else' condition to the startElement 
method (replace line 574 with):

             } else if (atts.getURI(i) != null && 
atts.getURI(i).length() > 0) {
                 // the localname and qName are the same, but there is a
                 // Namspace URI. We need to figure out the namespace 
prefix.
                 // this is an unusual condition. Currently the only 
known trigger
                 // is when there is a fixed/defaulted attribute from a 
validating
                 // XMLSchema, and the attribute is in a different namespace
                 // than the rest of the document, this happens whenever 
there
                 // is an attribute definition that has form="qualified".
                 // <xs:attribute name="attname" form="qualified" ... />
                 // or the schema sets attributeFormDefault="qualified"
                 String attURI = atts.getURI(i);
                 Namespace attNS = null;
                 Element p = element;
                 // We need to ensure that a particular prefix has not been
                 // overridden at a lower level than what we are expecting.
                 // track all prefixes to ensure they are not changed lower
                 // down.
                 HashSet overrides = new HashSet();
                 uploop: do {
                     // Search up the Element tree looking for a 
prefixed namespace
                     // matching our attURI
                     if (p.getNamespace().getURI().equals(attURI)
&& !overrides.contains(p.getNamespacePrefix())
&& !"".equals(element.getNamespace().getPrefix())) {
                         // we need a prefix. It's impossible to have a 
namespaced
                         // attribute if there is no prefix for that 
attribute.
                         attNS = p.getNamespace();
                         break uploop;
                     }
                     overrides.add(p.getNamespacePrefix());
                     for (Iterator it = 
p.getAdditionalNamespaces().iterator();
                             it.hasNext(); ) {
                         Namespace ns = (Namespace)it.next();
                         if (!overrides.contains(ns.getPrefix())
&& attURI.equals(ns.getURI())) {
                             attNS = ns;
                             break uploop;
                         }
                         overrides.add(ns.getPrefix());
                     }
                     if (p == element) {
                         p = currentElement;
                     } else {
                         p = p.getParentElement();
                     }
                 } while (p != null);
                 if (attNS == null) {
                     // we cannot find a 'prevailing' namespace that has 
a prefix
                     // that is for this namespace.
                     // This basically means that there's an XMLSchema, 
for the
                     // DEFAULT namespace, and there's a defaulted/fixed
                     // attribute definition in the XMLSchema that's 
targeted
                     // for this namespace,... but, the user has either not
                     // declared a prefixed version of the namespace, or has
                     // re-declared the same prefix at a lower level with a
                     // different namespace.
                     // All of these things are possible.
                     // Create some sort of default prefix.
                     int cnt = 0;
                     String base = "attns";
                     String pfx = base + cnt;
                     while (overrides.contains(pfx)) {
                         cnt++;
                         pfx = base + cnt;
                     }
                     attNS = Namespace.getNamespace(pfx, attURI);
                 }
                 attribute = factory.attribute(attLocalName, 
atts.getValue(i),
                         attType, attNS);
             } else {


Rolf

On 07/20/2011 09:23 AM, Thomas Scheffler wrote:
> Hi,
>
> if I parse a valid MODS document with XML Schema validation, JDOM 
> changes attributes as it handles default values of schema not 
> correctly (by ignoring the namespace).
>
> Here is a short code to demonstrate this:
>
> SAXBuilder builder = new SAXBuilder(true);
> builder.setFeature("http://xml.org/sax/features/namespaces", true);
> builder.setFeature("http://xml.org/sax/features/namespace-prefixes", 
> true);
> builder.setFeature("http://apache.org/xml/features/validation/schema", 
> true);
>
> Document document = builder.build(new 
> URL("http://academiccommons.columbia.edu/download/fedora_content/show_pretty/ac:111060/CONTENT/ac111060_description.xml"));
> XMLOutputter xout = new XMLOutputter(Format.getPrettyFormat());
> xout.output(document, System.out);
>
> Here is a result fragment:
>
> <name type="simple">
> <namePart type="family">Edwards</namePart>
> <namePart type="given">Stephen A.</namePart>
> <role>
> <roleTerm type="text">author</roleTerm>
> </role>
> <affiliation>Columbia University. Computer Science</affiliation>
> </name>
>
> If you look at the original document you can see, that @type of name 
> is "personal". The "simple" comes from the xlink XML-Schema that was 
> included by the MODS-Schema. Therefor the result fragment should look 
> like this:
>
> <name type="personal" xlink:type="simple">
> <namePart type="family">Edwards</namePart>
> <namePart type="given">Stephen A.</namePart>
> <role>
> <roleTerm type="text">author</roleTerm>
> </role>
> <affiliation>Columbia University. Computer Science</affiliation>
> </name>
>
> If I use DOM from Java this is done correctly (but a bit ugly as it 
> does not use the namespace prefix already defined).
>
> Could someone just fix this, please?
>
> Regards,
>
> Thomas Scheffler
> _______________________________________________
> To control your jdom-interest membership:
> http://www.jdom.org/mailman/options/jdom-interest/[email protected]
>

 
--------------------------------------------------------------------------
This email and any files transmitted with it are confidential and proprietary to Algorithmics Incorporated and its affiliates ("Algorithmics"). If received in error, use is prohibited. Please destroy, and notify sender. Sender does not waive confidentiality or privilege. Internet communications cannot be guaranteed to be timely, secure, error or virus-free. Algorithmics does not accept liability for any errors or omissions. Any commitment intended to bind Algorithmics must be reduced to writing and signed by an authorized signatory.
--------------------------------------------------------------------------

_______________________________________________
To control your jdom-interest membership:
http://www.jdom.org/mailman/options/jdom-interest/[email protected]
TestSAXComplexSchema.java (text/plain, 2.5 KB)
/**
 * 
 */
package org.jdom.test.cases.input;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URL;
import java.util.Iterator;
import java.util.List;

import org.jdom.*;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

/**
 * @author rlear
 *
 */
public class TestSAXComplexSchema extends TestCase {

	
    /**
     * The main method runs all the tests in the text ui
     */
    public static void main (String args[]) 
     {
        junit.textui.TestRunner.run(suite());
    }

    /**
     * The suite method runs all the tests
     */
    public static Test suite () {
        return new TestSuite(TestSAXComplexSchema.class);
    }
	
	/**
	 * Test method for {@link org.jdom.input.SAXBuilder#build(java.io.File)}.
	 */
	public void testBuildFile() throws IOException {
		SAXBuilder builder = new SAXBuilder(true);
		builder.setFeature("http://xml.org/sax/features/namespaces", true);
		builder.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
		builder.setFeature("http://apache.org/xml/features/validation/schema", true);
		
		File inputdir = new File(".");
		URL furl = inputdir.toURI().toURL();
		URL rurl = new URL(furl, "resources/SAXTestComplexNamespaces.xml");
		
		
		try {
			Document doc = builder.build(rurl);
			XMLOutputter out = new XMLOutputter(Format.getPrettyFormat());
			StringWriter sw = new StringWriter();
			out.output(doc, sw);
			String xml = sw.toString();
			System.out.println("Document parsed. Content:\n" + xml + "\n");
			
			Namespace defns = Namespace.getNamespace("http://www.jdom.org/tests/default");
			Namespace impns = Namespace.getNamespace("http://www.jdom.org/tests/imp");
			
			Element root = doc.getRootElement();
			assertTrue(root != null);
			assertTrue("test".equals(root.getName()));
			List kids = root.getChildren("data", defns);
			for (Iterator it = kids.iterator(); it.hasNext(); ) {
				Element data = (Element)it.next();
				assertTrue(defns.equals(data.getNamespace()));
				Attribute att = data.getAttribute("type", Namespace.NO_NAMESPACE);
				assertTrue("Could not find type attribute in default ns.", att != null);
				att = data.getAttribute("type", impns);
				assertTrue("Could not find type attribute in impns.", att != null);
			}
		} catch (JDOMException e) {
			e.printStackTrace();
			fail("Parsing failed. See stack trace.");
		}
		
	}

}
SAXTestComplexImport.xsd (text/xml, 471 B)
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns="http://www.jdom.org/tests/imp"
        xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.jdom.org/tests/imp"
        elementFormDefault="qualified">


  <xs:attributeGroup name="simpleAG">
    <xs:attribute name="type" type="xs:string" fixed="simple" form="qualified" /> 
    <xs:attribute name="nodup" type="xs:string" fixed="impval" form="qualified" /> 
  </xs:attributeGroup>

</xs:schema>
SAXTestComplexMain.xsd (text/xml, 921 B)
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns="http://www.jdom.org/tests/default"
        xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.jdom.org/tests/default"
        xmlns:imp="http://www.jdom.org/tests/imp" 
        elementFormDefault="qualified">

  <!--          xmlns:tns="http://www.jdom.org/tests/default"
       -->
  <xs:import namespace="http://www.jdom.org/tests/imp" schemaLocation="./SAXTestComplexImport.xsd" />
  
  <xs:element name="test">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="data" minOccurs="1" maxOccurs="unbounded">
          <xs:complexType>
            <xs:attribute name="type"/>
            <xs:attributeGroup ref="imp:simpleAG"/>
            <xs:attribute name="mainxs" default="mainval" form="qualified"/>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

</xs:schema>
SAXTestComplexNamespaces.xml (text/xml, 436 B)
<?xml version="1.0" encoding="ISO-8859-1"?>
<test xmlns="http://www.jdom.org/tests/default"
      xmlns:imp="http://www.jdom.org/tests/imp" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xsi:schemaLocation="http://www.jdom.org/tests/default ./SAXTestComplexMain.xsd">
   <data type="one" />
   <data type="two" />
   <data type="three" xmlns:redo="http://www.jdom.org/tests/default" redo:mainxs="change" />
</test>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.