More Atom parsing work

This commit is contained in:
jamesagnew 2014-02-27 11:57:50 -05:00
parent b92cc66804
commit bec9a51351
5 changed files with 204 additions and 167 deletions

View File

@ -9,7 +9,7 @@ import ca.uhn.fhir.model.primitive.StringDt;
public class Bundle implements IElement {
private StringDt myAuthorDevice;
private StringDt myAuthorUri;
private StringDt myAuthorName;
private List<BundleEntry> myEntries;
private StringDt myId;
@ -24,11 +24,11 @@ public class Bundle implements IElement {
private IntegerDt myTotalResults;
private InstantDt myUpdated;
public StringDt getAuthorDevice() {
if (myAuthorDevice == null) {
myAuthorDevice = new StringDt();
public StringDt getAuthorUri() {
if (myAuthorUri == null) {
myAuthorUri = new StringDt();
}
return myAuthorDevice;
return myAuthorUri;
}
public StringDt getAuthorName() {

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.model.api;
import org.apache.commons.lang3.StringUtils;
public class ResourceReference implements IElement {
@ -22,4 +24,8 @@ public class ResourceReference implements IElement {
myReference = theReference;
}
public boolean hasContent() {
return StringUtils.isNotBlank(myDisplay) || StringUtils.isNotBlank(myReference);
}
}

View File

@ -83,4 +83,8 @@ public class XhtmlDt implements IPrimitiveDatatype<List<XMLEvent>> {
myValue = theValue;
}
public boolean hasContent() {
return myValue != null && myValue.size() > 0;
}
}

View File

@ -9,6 +9,8 @@ import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import org.apache.commons.lang3.StringUtils;
import com.ctc.wstx.sw.BaseStreamWriter;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
@ -36,6 +38,34 @@ import ca.uhn.fhir.model.primitive.XhtmlDt;
class ParserState<T extends IElement> {
public class AtomAuthorState extends BaseState {
private Bundle myInstance;
public AtomAuthorState(Bundle theInstance) {
myInstance = theInstance;
}
@Override
public void endingElement(EndElement theElem) throws DataFormatException {
pop();
}
@Override
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
if ("name".equals(theLocalPart)) {
push(new AtomPrimitiveState(myInstance.getAuthorName()));
}else if ("uri".equals(theLocalPart)) {
push(new AtomPrimitiveState(myInstance.getAuthorUri()));
}else {
throw new DataFormatException("Unexpected element: " + theLocalPart);
}
}
}
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ParserState.class);
private FhirContext myContext;
@ -54,8 +84,8 @@ class ParserState<T extends IElement> {
myState.endingElement(theElem);
}
public void enteringNewElement(StartElement theElement, String theName) throws DataFormatException {
myState.enteringNewElement(theElement, theName);
public void enteringNewElement(String theNamespaceURI, String theName) throws DataFormatException {
myState.enteringNewElement(theNamespaceURI, theName);
}
public void enteringNewElementExtension(StartElement theElem, String theUrlAttr) {
@ -70,10 +100,6 @@ class ParserState<T extends IElement> {
return myObject != null;
}
public void otherEvent(XMLEvent theEvent) throws DataFormatException {
myState.otherEvent(theEvent);
}
private void pop() {
myState = myState.myStack;
myState.wereBack();
@ -105,10 +131,6 @@ class ParserState<T extends IElement> {
myPrimitive = thePrimitive;
}
@Override
public void attributeValue(Attribute theAttribute, String theValue) throws DataFormatException {
// ignore
}
@Override
public void endingElement(EndElement theElem) throws DataFormatException {
@ -117,7 +139,7 @@ class ParserState<T extends IElement> {
}
@Override
public void enteringNewElement(StartElement theElement, String theLocalPart) throws DataFormatException {
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
throw new DataFormatException("Unexpected nested element in atom tag ");
}
@ -127,49 +149,65 @@ class ParserState<T extends IElement> {
}
@Override
public void otherEvent(XMLEvent theEvent) throws DataFormatException {
if (theEvent.isCharacters()) {
String data = theEvent.asCharacters().getData();
if (myData == null) {
myData = data;
} else {
// this shouldn't generally happen so it's ok that it's inefficient
myData = myData + data;
}
public void string(String theData) {
if (myData == null) {
myData = theData;
} else {
// this shouldn't generally happen so it's ok that it's inefficient
myData = myData + theData;
}
}
}
private class ExpectEndElementState extends BaseState {
private class AtomLinkState extends BaseState {
private String myRel;
private String myHref;
private Bundle myInstance;
public AtomLinkState(Bundle theInstance) {
myInstance = theInstance;
}
@Override
public void attributeValue(Attribute theAttribute, String theValue) throws DataFormatException {
throw new DataFormatException("Found unexpected element content");
String name = theAttribute.getName().getLocalPart();
if ("rel".equals(name)) {
myRel = theValue;
} else if ("href".equals(name)) {
myHref = theValue;
}
}
@Override
public void endingElement(EndElement theElem) throws DataFormatException {
if ("self".equals(myRel)) {
myInstance.getLinkSelf().setValueAsString(myHref);
} else if ("first".equals(myRel)) {
myInstance.getLinkFirst().setValueAsString(myHref);
} else if ("previous".equals(myRel)) {
myInstance.getLinkPrevious().setValueAsString(myHref);
} else if ("next".equals(myRel)) {
myInstance.getLinkNext().setValueAsString(myHref);
} else if ("last".equals(myRel)) {
myInstance.getLinkLast().setValueAsString(myHref);
} else if ("fhir-base".equals(myRel)) {
myInstance.getLinkBase().setValueAsString(myHref);
}
pop();
}
@Override
public void enteringNewElement(StartElement theElement, String theLocalPart) throws DataFormatException {
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
throw new DataFormatException("Found unexpected element content");
}
@Override
protected IElement getCurrentElement() {
return null;
}
@Override
public void otherEvent(XMLEvent theEvent) throws DataFormatException {
throw new DataFormatException("Found unexpected element content");
}
}
private static final QName ATOM_LINK_REL_ATTRIBUTE = new QName("rel");
private static final QName ATOM_LINK_HREF_ATTRIBUTE = new QName("href");
@ -193,36 +231,22 @@ class ParserState<T extends IElement> {
}
@Override
public void enteringNewElement(StartElement theElement, String theLocalPart) throws DataFormatException {
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
if (theLocalPart.equals("title")) {
push(new AtomPrimitiveState(myInstance.getTitle()));
} else if ("id".equals(theLocalPart)) {
push(new AtomPrimitiveState(myInstance.getId()));
} else if ("link".equals(theLocalPart)) {
Attribute rel = theElement.getAttributeByName(ATOM_LINK_REL_ATTRIBUTE);
Attribute href = theElement.getAttributeByName(ATOM_LINK_HREF_ATTRIBUTE);
if (rel != null && href != null) {
if ("self".equals(rel.getValue())) {
myInstance.getLinkSelf().setValueAsString(href.getValue());
push(new ExpectEndElementState());
} else if ("first".equals(rel.getValue())) {
myInstance.getLinkFirst().setValueAsString(href.getValue());
push(new ExpectEndElementState());
} else if ("previous".equals(rel.getValue())) {
myInstance.getLinkPrevious().setValueAsString(href.getValue());
push(new ExpectEndElementState());
} else if ("next".equals(rel.getValue())) {
myInstance.getLinkNext().setValueAsString(href.getValue());
push(new ExpectEndElementState());
} else if ("last".equals(rel.getValue())) {
myInstance.getLinkLast().setValueAsString(href.getValue());
push(new ExpectEndElementState());
} else if ("fhir-base".equals(rel.getValue())) {
myInstance.getLinkBase().setValueAsString(href.getValue());
push(new ExpectEndElementState());
}
}
}
push(new AtomLinkState(myInstance));
} else if ("totalresults".equals(theLocalPart) && verifyNamespace(XmlParser.OPENSEARCH_NS, theNamespaceURI)) {
push(new AtomPrimitiveState(myInstance.getTotalResults()));
} else if ("updated".equals(theLocalPart)) {
push(new AtomPrimitiveState(myInstance.getUpdated()));
} else if ("author".equals(theLocalPart)) {
push(new AtomAuthorState(myInstance));
}
// TODO: handle category and DSig
}
@Override
@ -230,11 +254,6 @@ class ParserState<T extends IElement> {
return myInstance;
}
@Override
public void otherEvent(XMLEvent theEvent) throws DataFormatException {
// ignore
}
}
private class PreAtomState extends BaseState {
@ -252,7 +271,7 @@ class ParserState<T extends IElement> {
}
@Override
public void enteringNewElement(StartElement theElement, String theLocalPart) throws DataFormatException {
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
if (!"feed".equals(theLocalPart)) {
throw new DataFormatException("Expecting outer element called 'feed', found: " + theLocalPart);
}
@ -274,22 +293,26 @@ class ParserState<T extends IElement> {
myObject = (T) myInstance;
}
@Override
public void otherEvent(XMLEvent theEvent) throws DataFormatException {
// ignore
}
}
private abstract class BaseState {
private BaseState myStack;
public abstract void attributeValue(Attribute theAttribute, String theValue) throws DataFormatException;
@SuppressWarnings("unused")
public void attributeValue(Attribute theAttribute, String theValue) throws DataFormatException {
// ignore by default
}
public abstract void endingElement(EndElement theElem) throws DataFormatException;
@SuppressWarnings("unused")
public void endingElement(EndElement theElem) throws DataFormatException {
// ignore by default
}
public abstract void enteringNewElement(StartElement theElement, String theLocalPart) throws DataFormatException;
@SuppressWarnings("unused")
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
// ignore by default
}
/**
* Default implementation just handles undeclared extensions
@ -305,9 +328,9 @@ class ParserState<T extends IElement> {
}
}
protected abstract IElement getCurrentElement();
public abstract void otherEvent(XMLEvent theEvent) throws DataFormatException;
protected IElement getCurrentElement() {
return null;
}
public void setStack(BaseState theState) {
myStack = theState;
@ -317,6 +340,14 @@ class ParserState<T extends IElement> {
// allow an implementor to override
}
public void string(@SuppressWarnings("unused") String theData) {
// ignore by default
}
public void xmlEvent(@SuppressWarnings("unused") XMLEvent theNextEvent) {
// ignore
}
}
private class DeclaredExtensionState extends BaseState {
@ -341,7 +372,7 @@ class ParserState<T extends IElement> {
}
@Override
public void enteringNewElement(StartElement theElement, String theLocalPart) throws DataFormatException {
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
BaseRuntimeElementDefinition<?> target = myDefinition.getChildByName(theLocalPart);
if (target == null) {
throw new DataFormatException("Unknown extension element name: " + theLocalPart);
@ -402,11 +433,6 @@ class ParserState<T extends IElement> {
return myParentInstance;
}
@Override
public void otherEvent(XMLEvent theEvent) throws DataFormatException {
// ignore
}
}
private class ElementCompositeState extends BaseState {
@ -434,7 +460,7 @@ class ParserState<T extends IElement> {
}
@Override
public void enteringNewElement(StartElement theElement, String theChildName) throws DataFormatException {
public void enteringNewElement(String theNamespace, String theChildName) throws DataFormatException {
BaseRuntimeChildDefinition child = myDefinition.getChildByNameOrThrowDataFormatException(theChildName);
BaseRuntimeElementDefinition<?> target = child.getChildByName(theChildName);
if (target == null) {
@ -478,7 +504,7 @@ class ParserState<T extends IElement> {
RuntimePrimitiveDatatypeNarrativeDefinition xhtmlTarget = (RuntimePrimitiveDatatypeNarrativeDefinition) target;
XhtmlDt newDt = xhtmlTarget.newInstance();
child.getMutator().addValue(myInstance, newDt);
XhtmlState state = new XhtmlState(newDt, theElement);
XhtmlState state = new XhtmlState(newDt);
push(state);
return;
}
@ -508,11 +534,6 @@ class ParserState<T extends IElement> {
return myInstance;
}
@Override
public void otherEvent(XMLEvent theEvent) {
// ignore
}
}
private class ExtensionState extends BaseState {
@ -537,7 +558,7 @@ class ParserState<T extends IElement> {
}
@Override
public void enteringNewElement(StartElement theElement, String theLocalPart) throws DataFormatException {
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
BaseRuntimeElementDefinition<?> target = myContext.getRuntimeChildUndeclaredExtensionDefinition().getChildByName(theLocalPart);
if (target == null) {
throw new DataFormatException("Unknown extension element name: " + theLocalPart);
@ -581,11 +602,6 @@ class ParserState<T extends IElement> {
return myExtension;
}
@Override
public void otherEvent(XMLEvent theEvent) throws DataFormatException {
// ignore
}
}
private class PreResourceState extends BaseState {
@ -603,7 +619,7 @@ class ParserState<T extends IElement> {
}
@Override
public void enteringNewElement(StartElement theElement, String theLocalPart) throws DataFormatException {
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
BaseRuntimeElementDefinition<?> definition = myContext.getNameToResourceDefinition().get(theLocalPart);
if (!(definition instanceof RuntimeResourceDefinition)) {
throw new DataFormatException("Element '" + theLocalPart + "' is not a resource, expected a resource at this position");
@ -620,11 +636,6 @@ class ParserState<T extends IElement> {
return myInstance;
}
@Override
public void otherEvent(XMLEvent theEvent) throws DataFormatException {
// ignore
}
@SuppressWarnings("unchecked")
@Override
public void wereBack() {
@ -652,7 +663,7 @@ class ParserState<T extends IElement> {
}
@Override
public void enteringNewElement(StartElement theElement, String theLocalPart) throws DataFormatException {
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
throw new Error("?? can this happen?"); // TODO: can this happen?
}
@ -661,11 +672,6 @@ class ParserState<T extends IElement> {
return myInstance;
}
@Override
public void otherEvent(XMLEvent theEvent) {
// ignore
}
}
private class ResourceReferenceState extends BaseState {
@ -707,7 +713,7 @@ class ParserState<T extends IElement> {
}
@Override
public void enteringNewElement(StartElement theElem, String theLocalPart) throws DataFormatException {
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
switch (mySubState) {
case INITIAL:
if ("display".equals(theLocalPart)) {
@ -729,11 +735,6 @@ class ParserState<T extends IElement> {
return myInstance;
}
@Override
public void otherEvent(XMLEvent theEvent) {
// ignore
}
}
private enum ResourceReferenceSubState {
@ -745,32 +746,9 @@ class ParserState<T extends IElement> {
private XhtmlDt myDt;
private List<XMLEvent> myEvents = new ArrayList<XMLEvent>();
private XhtmlState(XhtmlDt theXhtmlDt, StartElement theXhtmlStartElement) throws DataFormatException {
private XhtmlState(XhtmlDt theXhtmlDt) throws DataFormatException {
myDepth = 1;
myDt = theXhtmlDt;
myEvents.add(theXhtmlStartElement);
}
@Override
public void attributeValue(Attribute theAttr, String theValue) throws DataFormatException {
myEvents.add(theAttr);
}
@Override
public void endingElement(EndElement theElement) throws DataFormatException {
myEvents.add(theElement);
myDepth--;
if (myDepth == 0) {
myDt.setValue(myEvents);
pop();
}
}
@Override
public void enteringNewElement(StartElement theElem, String theLocalPart) throws DataFormatException {
myDepth++;
myEvents.add(theElem);
}
@Override
@ -779,9 +757,36 @@ class ParserState<T extends IElement> {
}
@Override
public void otherEvent(XMLEvent theEvent) throws DataFormatException {
public void xmlEvent(XMLEvent theEvent) {
myEvents.add(theEvent);
if (theEvent.isStartElement()) {
myDepth++;
}
if (theEvent.isEndElement()) {
myDepth--;
if (myDepth == 0) {
myDt.setValue(myEvents);
pop();
}
}
}
}
public void string(String theData) {
myState.string(theData);
}
public boolean verifyNamespace(String theExpect, String theActual) {
return StringUtils.equals(theExpect, theActual);
}
/**
* Invoked after any new XML event is individually processed, containing a copy of the XML event. This is basically intended for embedded XHTML content
*/
public void xmlEvent(XMLEvent theNextEvent) {
myState.xmlEvent(theNextEvent);
}
}

View File

@ -49,12 +49,12 @@ import ca.uhn.fhir.model.primitive.XhtmlDt;
import ca.uhn.fhir.util.PrettyPrintWriterWrapper;
public class XmlParser {
private static final String ATOM_NS = "http://www.w3.org/2005/Atom";
private static final String FHIR_NS = "http://hl7.org/fhir";
private static final String OPENSEARCH_NS = "http://a9.com/-/spec/opensearch/1.1/";
static final String ATOM_NS = "http://www.w3.org/2005/Atom";
static final String FHIR_NS = "http://hl7.org/fhir";
static final String OPENSEARCH_NS = "http://a9.com/-/spec/opensearch/1.1/";
@SuppressWarnings("unused")
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParser.class);
private static final String XHTML_NS = "http://www.w3.org/1999/xhtml";
static final String XHTML_NS = "http://www.w3.org/1999/xhtml";
// private static final Set<String> RESOURCE_NAMESPACES;
@ -106,7 +106,7 @@ public class XmlParser {
if (StringUtils.isNotBlank(theBundle.getAuthorName().getValue())) {
eventWriter.writeStartElement("author");
writeTagWithTextNode(eventWriter, "name", theBundle.getAuthorName());
writeOptionalTagWithTextNode(eventWriter, "device", theBundle.getAuthorDevice());
writeOptionalTagWithTextNode(eventWriter, "uri", theBundle.getAuthorUri());
}
for (BundleEntry nextEntry : theBundle.getEntries()) {
@ -131,15 +131,21 @@ public class XmlParser {
return stringWriter.toString();
}
private void encodeChildElementToStreamWriter(XMLStreamWriter theEventWriter, IElement nextValue, String childName, BaseRuntimeElementDefinition<?> childDef, String theExtensionUrl) throws XMLStreamException, DataFormatException {
private boolean encodeChildElementToStreamWriter(XMLStreamWriter theEventWriter, IElement nextValue, String childName, BaseRuntimeElementDefinition<?> childDef, String theExtensionUrl)
throws XMLStreamException, DataFormatException {
switch (childDef.getChildType()) {
case PRIMITIVE_DATATYPE: {
theEventWriter.writeStartElement(childName);
IPrimitiveDatatype<?> pd = (IPrimitiveDatatype<?>) nextValue;
theEventWriter.writeAttribute("value", pd.getValueAsString());
encodeExtensionsIfPresent(theEventWriter, nextValue);
theEventWriter.writeEndElement();
break;
String value = pd.getValueAsString();
if (value != null) {
theEventWriter.writeStartElement(childName);
theEventWriter.writeAttribute("value", value);
encodeExtensionsIfPresent(theEventWriter, nextValue);
theEventWriter.writeEndElement();
return true;
} else {
return false;
}
}
case RESOURCE_BLOCK:
case COMPOSITE_DATATYPE: {
@ -151,30 +157,41 @@ public class XmlParser {
encodeCompositeElementToStreamWriter(nextValue, theEventWriter, childCompositeDef);
encodeExtensionsIfPresent(theEventWriter, nextValue);
theEventWriter.writeEndElement();
break;
return true;
}
case RESOURCE_REF: {
theEventWriter.writeStartElement(childName);
ResourceReference ref = (ResourceReference) nextValue;
encodeResourceReferenceToStreamWriter(theEventWriter, ref);
theEventWriter.writeEndElement();
break;
if (ref.hasContent()) {
theEventWriter.writeStartElement(childName);
encodeResourceReferenceToStreamWriter(theEventWriter, ref);
theEventWriter.writeEndElement();
return true;
} else {
return false;
}
}
case RESOURCE: {
throw new IllegalStateException(); // should not happen
}
case PRIMITIVE_XHTML: {
XhtmlDt dt = (XhtmlDt) nextValue;
encodeXhtml(dt, theEventWriter);
break;
if (dt.hasContent()) {
encodeXhtml(dt, theEventWriter);
return true;
} else {
return false;
}
}
case UNDECL_EXT: {
throw new IllegalStateException("should not happen");
}
}
return false;
}
private void encodeCompositeElementChildrenToStreamWriter(IElement theElement, XMLStreamWriter theEventWriter, List<? extends BaseRuntimeChildDefinition> children) throws XMLStreamException, DataFormatException {
private void encodeCompositeElementChildrenToStreamWriter(IElement theElement, XMLStreamWriter theEventWriter, List<? extends BaseRuntimeChildDefinition> children) throws XMLStreamException,
DataFormatException {
for (BaseRuntimeChildDefinition nextChild : children) {
List<? extends IElement> values = nextChild.getAccessor().getValues(theElement);
if (values == null || values.isEmpty()) {
@ -205,15 +222,18 @@ public class XmlParser {
}
}
private void encodeCompositeElementToStreamWriter(IElement theElement, XMLStreamWriter theEventWriter, BaseRuntimeElementCompositeDefinition<?> resDef) throws XMLStreamException, DataFormatException {
private void encodeCompositeElementToStreamWriter(IElement theElement, XMLStreamWriter theEventWriter, BaseRuntimeElementCompositeDefinition<?> resDef) throws XMLStreamException,
DataFormatException {
encodeExtensionsIfPresent(theEventWriter, theElement);
encodeCompositeElementChildrenToStreamWriter(theElement, theEventWriter, resDef.getExtensions());
encodeCompositeElementChildrenToStreamWriter(theElement, theEventWriter, resDef.getChildren());
}
private void encodeExtensionsIfPresent(XMLStreamWriter theWriter, IElement theResource) throws XMLStreamException, DataFormatException {
boolean retVal = false;
if (theResource instanceof ISupportsUndeclaredExtensions) {
for (UndeclaredExtension next : ((ISupportsUndeclaredExtensions) theResource).getUndeclaredExtensions()) {
retVal =true;
theWriter.writeStartElement("extension");
theWriter.writeAttribute("url", next.getUrl());
@ -388,9 +408,6 @@ public class XmlParser {
StartElement elem = nextEvent.asStartElement();
String namespaceURI = elem.getName().getNamespaceURI();
if (!FHIR_NS.equals(namespaceURI) && !XHTML_NS.equals(namespaceURI)) {
continue;
}
if ("extension".equals(elem.getName().getLocalPart())) {
Attribute urlAttr = elem.getAttributeByName(new QName("url"));
@ -399,7 +416,10 @@ public class XmlParser {
}
parserState.enteringNewElementExtension(elem, urlAttr.getValue());
} else {
parserState.enteringNewElement(elem, elem.getName().getLocalPart());
String elementName = elem.getName().getLocalPart();
parserState.enteringNewElement(namespaceURI, elementName);
}
for (@SuppressWarnings("unchecked")
@ -430,10 +450,12 @@ public class XmlParser {
if (parserState.isComplete()) {
return parserState.getObject();
}
} else {
parserState.otherEvent(nextEvent);
} else if (nextEvent.isCharacters()) {
parserState.string(nextEvent.asCharacters().getData());
}
parserState.xmlEvent(nextEvent);
}
return null;