//
// Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s).
// All rights reserved.
//
package openadk.library.impl.surrogates;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import openadk.library.*;
import openadk.util.XMLWriter;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.JXPathException;
import org.apache.commons.jxpath.ri.model.NodePointer;
/**
* Implements a flexible Rendering surrogate based on a proprietary XPath syntax
* @author Andrew
*
*/
public class XPathSurrogate extends AbstractRenderSurrogate implements RenderSurrogate {
private String fLegacyXpath;
private String fValueXpath;
public XPathSurrogate( ElementDef def, String xpathMacro )
{
super( def );
String[] macroParts = xpathMacro.split( "=" );
fLegacyXpath = macroParts[0];
fValueXpath = macroParts[1];
}
public void renderRaw(
XMLWriter writer,
SIFVersion version,
Element o,
SIFFormatter formatter)
{
// Read the value out of the source object
Element valueElement = findReferencedElement( o );
if( valueElement == null ){
return;
}
SIFSimpleType value = valueElement.getSIFValue();
if( value == null || value.getValue() == null ){
return;
}
String[] xPathParts = fLegacyXpath.split( "/" );
int currentSegment = 0;
// Build the path
while( currentSegment < xPathParts.length - 1 )
{
writer.tab();
writer.write( '<' );
writer.write( xPathParts[ currentSegment ] );
if( !xPathParts[currentSegment+1].startsWith( "@" ) ){
writer.write( '>' );
}
currentSegment++;
}
String finalSegment = xPathParts[currentSegment];
if( finalSegment.startsWith( "@" ) ){
writer.write( ' ' );
writer.write( finalSegment.substring( 1 ) );
writer.write( "=\"" );
writer.printXmlText( value.toString( formatter ) );
writer.write( "\" />\r\n" );
currentSegment--;
} else {
// Note: finalSegment can be equal to ".", which
// signals to render the text only
if( finalSegment.length() > 1 )
{
writer.tab();
writer.write( '<' );
writer.write( finalSegment );
writer.write( '>' );
}
writer.printXmlText( value.toString( formatter ) );
if( finalSegment.length() > 1 )
{
writer.write( "</" );
writer.write( finalSegment );
writer.write( ">\r\n" );
}
}
currentSegment--;
// unwind the path
while( currentSegment > -1 )
{
writer.write( "</" );
writer.write( xPathParts[ currentSegment ] );
writer.write( ">\r\n" );
currentSegment--;
}
}
public boolean readRaw(
XMLStreamReader reader,
SIFVersion version,
SIFElement parent,
SIFFormatter formatter )
throws ADKParsingException
{
String value = null;
//
// STEP 1
// Determine if this surrogate can handle the parsing of this node.
// Retrieve the node value as a string
//
String[] xPathParts = fLegacyXpath.split( "/" );
int eventType = reader.getEventType();
String localName = reader.getLocalName();
if ( eventType == XMLStreamConstants.START_ELEMENT &&
localName.equals( xPathParts[0] ) )
{
try
{
int currentSegment = 0;
int lastSegment = xPathParts.length - 1;
if( xPathParts[lastSegment].startsWith( "@" ) ){
lastSegment--;
}
while( currentSegment < lastSegment )
{
reader.nextTag();
currentSegment++;
if( !reader.getLocalName().equals( xPathParts[currentSegment] ) ){
throwParseException("Element {" + reader.getLocalName() + "} is not supported by XPathSurrogate path " + fLegacyXpath, version );
}
}
// New we are at the last segment in the XPath, and the XMLStreamReader
// should be positioned on the proper node. The last segment is either
// an attribute or an element, which need to be read differently
String finalSegment = xPathParts[ xPathParts.length - 1 ];
if( finalSegment.startsWith( "@" ) ){
value = reader.getAttributeValue( null, finalSegment.substring( 1 ) );
// Advance the cursor
reader.next();
} else {
value = readElementTextValue( reader );
}
super.nextTag( reader );
}
catch( XMLStreamException xse )
{
throwParseException( xse, reader.getLocalName(), version );
}
} else {
// No match was found
return false;
}
//
// STEP 2
// Find the actual field to set the value to
//
ElementDef fieldDef = null;
SIFElement targetElement = parent;
if(fValueXpath.equals( "." ) && fElementDef.isField() ){
fieldDef = fElementDef;
} else {
// This indicates a child SIFElement that needs to be created
try
{
targetElement = SIFElement.create( parent, fElementDef );
} catch (ADKSchemaException adkse ){
throwParseException( adkse, reader.getLocalName(), version );
}
formatter.addChild( parent, targetElement, version );
if( fValueXpath.equals( "." ) ){
fieldDef = fElementDef;
} else {
String fieldName = fValueXpath;
if( fValueXpath.startsWith( "@" ) ){
fieldName = fValueXpath.substring( 1 );
}
fieldDef = ADK.DTD().lookupElementDef( fElementDef, fieldName );
}
}
if( fieldDef == null ) {
throw new RuntimeException("Support for value path {" + fValueXpath + "} is not supported by XPathSurrogate." );
}
//
// STEP 3
// Set the value to the field
//
SIFTypeConverter converter = fieldDef.getTypeConverter();
if( converter == null ){
// TODO: Determine if we should be automatically creating a converter
// for elementDefs that don't have one, or whether we should just throw the
// spurious data away.
converter = SIFTypeConverters.STRING;
}
SIFSimpleType data = converter.parse( formatter, value );
targetElement.setField( fieldDef, data );
return true;
}
@Override
public String toString() {
return "XPathSurrogate{" + fLegacyXpath + "=" + fValueXpath + "}";
}
/**
* Creates the Child as the result of an XPath Expression and returns the
* NodePointer wrapping the child
*/
public NodePointer createChild(NodePointer parentPointer, SIFFormatter formatter, SIFVersion version, JXPathContext context ) {
// 1) Create an instance of the SimpleField with a null value (It's assigned later)
//
// STEP 2
// Find the actual field to set the value to
//
SIFElement parent = (SIFElement)parentPointer.getNode();
SIFElement targetElement = parent;
if( !fElementDef.isField() ){
// This indicates a child SIFElement that needs to be created
try
{
targetElement = SIFElement.create( parent, fElementDef );
} catch (ADKSchemaException adkse ){
throw new JXPathException( "Unable to create " + parent.getElementDef().name() + "/" +
fElementDef.name() );
}
formatter.addChild( parent, targetElement, version );
}
ElementDef fieldDef = null;
if( fValueXpath.equals( "." ) ){
fieldDef = fElementDef;
} else {
String fieldName = fValueXpath;
if( fValueXpath.startsWith( "@" ) ){
fieldName = fValueXpath.substring( 1 );
}
fieldDef = ADK.DTD().lookupElementDef( fElementDef, fieldName );
}
if( fieldDef == null ) {
throw new JXPathException("Support for value path {" + fValueXpath + "} is not supported by XPathSurrogate." );
}
SIFSimpleType ssf = fieldDef.getTypeConverter().getSIFSimpleType(
null);
SimpleField sf = ssf.createField(targetElement, fieldDef);
targetElement.setField(sf);
// 2) built out a fake set of node pointers representing the SIF 1.5r1 path and
// return the root pointer from that stack
return buildLegacyPointers( parentPointer, sf );
}
/**
* Creates a NodePointer that "wraps" the specified ADK 2.0 element, but appears to JXPath
* as if it uses the SIF 1.5 path.
*
* XPathSurrogates always represent a surrogate around an ADK 2.0 Simple Field
*
*/
public NodePointer createNodePointer(NodePointer parent, Element sourceElement, SIFVersion version) {
// 1) Find the field referenced by the XPathSurrogate expression
// If it doesn't exist, return null
Element referencedField = findReferencedElement( sourceElement );
if( referencedField == null ){
return null;
}
// 2) If it does exist, build out a fake set of node pointers representing the
// SIF 1.5r1 path and return the root pointer.
return buildLegacyPointers( parent, referencedField );
}
/**
* Builds a set of nodes to re-create a SIF 1.5 legacy path
* @param referencedField
* @return
*/
private NodePointer buildLegacyPointers( NodePointer parent, Element referencedField) {
String[] xPathParts = fLegacyXpath.split( "/" );
int currentSegment = 0;
NodePointer root = null;
NodePointer currentParent = parent;
// Build the path
while( currentSegment < xPathParts.length - 1 )
{
FauxSIFElementPointer pointer = new FauxSIFElementPointer( currentParent, xPathParts[currentSegment] );
if( currentParent != null && currentParent instanceof FauxSIFElementPointer ){
((FauxSIFElementPointer)currentParent).setChild( pointer, xPathParts[currentSegment], false );
}
currentParent = pointer;
if( root == null ){
root = pointer;
}
currentSegment++;
}
String finalSegment = xPathParts[currentSegment];
boolean isAttribute = false;
if( finalSegment.startsWith( "@" ) ){
// This is an attribute
finalSegment = finalSegment.substring( 1 );
isAttribute = true;
}
SurrogateSimpleFieldPointer fieldPointer = new SurrogateSimpleFieldPointer(currentParent, finalSegment, referencedField );
if( currentParent != null && currentParent instanceof FauxSIFElementPointer ){
((FauxSIFElementPointer)currentParent).setChild( fieldPointer, finalSegment, isAttribute );
}
if( root == null ){
root = fieldPointer;
}
return root;
}
/**
* Finds the element referenced by the expression after the '=' sign in
* the constructor for XPathSurrogate. This path represents the XPath from
* the element being wrapped to the actual field it represents.
* @param startOfPath
* @return
*/
private Element findReferencedElement( Element startOfPath ) {
// Read the value out of the source object
if( fValueXpath.startsWith( "." ) ){
return startOfPath;
} else {
ElementDef valueDef = null;
if( startOfPath instanceof SIFElement ){
valueDef = ADK.DTD().lookupElementDefBySQP( startOfPath.getElementDef(), fValueXpath );
}
if( valueDef == null ){
throw new RuntimeException("Support for value path {" + fValueXpath + "} is not supported by XPathSurrogate." );
}
SimpleField field = ((SIFElement)startOfPath).getField( valueDef );
return field;
}
}
public String getPath() {
return fLegacyXpath;
}
}