2323import java .util .stream .Stream ;
2424import java .util .stream .StreamSupport ;
2525
26+ import javax .xml .XMLConstants ;
27+ import javax .xml .namespace .NamespaceContext ;
2628import javax .xml .namespace .QName ;
2729import javax .xml .parsers .DocumentBuilder ;
2830import javax .xml .parsers .DocumentBuilderFactory ;
3739import org .eclipse .e4 .emf .xpath .XPathContext ;
3840import org .eclipse .e4 .emf .xpath .XPathContextFactory ;
3941import org .eclipse .e4 .emf .xpath .XPathNotFoundException ;
40- import org .eclipse .emf .ecore .EAttribute ;
41- import org .eclipse .emf .ecore .EClass ;
4242import org .eclipse .emf .ecore .EObject ;
43- import org .eclipse .emf .ecore .EStructuralFeature ;
44- import org .eclipse .emf .ecore .util .EcoreUtil ;
43+ import org .eclipse .emf .ecore .EcorePackage ;
44+ import org .eclipse .emf .ecore .xmi .XMLResource ;
45+ import org .eclipse .emf .ecore .xmi .impl .DefaultDOMHandlerImpl ;
46+ import org .eclipse .emf .ecore .xmi .impl .XMIHelperImpl ;
47+ import org .eclipse .emf .ecore .xmi .impl .XMLSaveImpl ;
4548import org .w3c .dom .Attr ;
4649import org .w3c .dom .Document ;
4750import org .w3c .dom .Element ;
51+ import org .w3c .dom .NamedNodeMap ;
4852import org .w3c .dom .Node ;
4953import org .w3c .dom .NodeList ;
5054
@@ -60,66 +64,51 @@ public XPathContext newContext(XPathContext parentContext, T contextBean) {
6064 if (!(contextBean instanceof EObject rootObject )) {
6165 throw new IllegalArgumentException ();
6266 }
67+ XPath xpath ;
68+ DOMMapping domMapping ;
69+ Element rootElement = null ;
70+
6371 if (parentContext != null ) {
64- EObjectContext parent = ((EObjectContext ) parentContext );
65- Element rootElement = parent .object2domProxy .get (contextBean );
66- if (rootElement == null ) {
67- throw new IllegalArgumentException ("Context bean is not from the same tree its parent context" );
68- }
69- return new EObjectContext (rootElement , parent );
72+ EObjectContext parent = (EObjectContext ) parentContext ;
73+ xpath = parent .xpath ;
74+ rootElement = parent .domMapping .getElement (contextBean );
75+ } else {
76+ xpath = XPATH_FACTORY .newXPath ();
7077 }
71- DocumentBuilder documentBuilder ;
72- try {
73- documentBuilder = DocumentBuilderFactory .newDefaultInstance ().newDocumentBuilder ();
74- } catch (ParserConfigurationException e ) {
75- throw new IllegalStateException (e );
76- }
77- Document document = documentBuilder .newDocument ();
78-
79- Map <Node , EObject > domProxy2object = new HashMap <>();
80- Map <EObject , Element > object2domProxy = new HashMap <>();
81- // The xpath '/' actually referes to the document but it's also expected to
82- // match the root application
83- domProxy2object .put (document , rootObject );
8478
85- Element rootElement = createElement (rootObject , document );
86- object2domProxy .put (rootObject , rootElement );
87- domProxy2object .put (rootElement , rootObject );
88-
89- rootObject .eAllContents ().forEachRemaining (eObject -> {
90- Element parent = object2domProxy .get (eObject .eContainer ());
91- Element proxy = object2domProxy .computeIfAbsent (eObject , o -> createElement (o , parent ));
92- domProxy2object .put (proxy , eObject );
93- });
79+ if (rootElement != null ) {
80+ domMapping = ((EObjectContext ) parentContext ).domMapping ;
81+ } else {
82+ DocumentBuilder documentBuilder ;
83+ try {
84+ documentBuilder = DocumentBuilderFactory .newDefaultInstance ().newDocumentBuilder ();
85+ } catch (ParserConfigurationException e ) {
86+ throw new IllegalStateException (e );
87+ }
88+ Document document = documentBuilder .newDocument ();
9489
95- return new EObjectContext (rootElement , domProxy2object , object2domProxy );
90+ domMapping = new DOMMapping ();
91+ rootElement = createElement (rootObject , document , domMapping );
92+ xpath .setNamespaceContext (createNamespaceContext (rootElement ));
93+ }
94+ return new EObjectContext (rootElement , domMapping , xpath );
9695 }
9796
98- private static class EObjectContext implements XPathContext {
97+ private static final XPathFactory XPATH_FACTORY = XPathFactory . newInstance ();
9998
100- private static final XPathFactory XPATH_FACTORY = XPathFactory . newInstance ();
99+ private static class EObjectContext implements XPathContext {
101100
102101 private final XPath xpath ;
103102 private final Element rootElement ;
104- private final Map <Node , EObject > domProxy2object ;
105- private final Map <EObject , Element > object2domProxy ;
103+ private final DOMMapping domMapping ;
106104
107- private EObjectContext (Element rootElement , Map <Node , EObject > domProxy2object ,
108- Map <EObject , Element > object2domProxy ) {
105+ private EObjectContext (Element rootElement , DOMMapping domMapping , XPath xpath ) {
109106 this .rootElement = rootElement ;
110- this .domProxy2object = Map .copyOf (domProxy2object );
111- this .object2domProxy = Map .copyOf (object2domProxy );
112- this .xpath = XPATH_FACTORY .newXPath ();
107+ this .domMapping = domMapping ;
108+ this .xpath = xpath ;
113109 this .xpath .setXPathFunctionResolver (this ::resolveEMFFunctions );
114110 }
115111
116- private EObjectContext (Element rootElement , EObjectContext parentContext ) {
117- this .rootElement = rootElement ;
118- this .domProxy2object = Map .copyOf (parentContext .domProxy2object );
119- this .object2domProxy = Map .copyOf (parentContext .object2domProxy );
120- this .xpath = parentContext .xpath ;
121- }
122-
123112 @ Override
124113 public <R > Stream <R > stream (String path , Class <R > resultType ) {
125114 // See XPathResultType for generally supported result types
@@ -151,7 +140,7 @@ public <R> Stream<R> stream(String path, Class<R> resultType) {
151140 }
152141 return StreamSupport .stream (pathNodes .spliterator (), false ).map (node -> {
153142 if (node instanceof Element || node instanceof Document ) {
154- return domProxy2object . get (node );
143+ return ( EObject ) domMapping . getValue (node );
155144 } else if (node instanceof Attr attribute ) {
156145 return attribute .getValue ();
157146 }
@@ -177,11 +166,12 @@ public Object getValue(String xpath) {
177166 }
178167
179168 private XPathFunction resolveEMFFunctions (QName functionName , int arity ) {
180- if (arity == 1 && "ecore" .equals (functionName .getNamespaceURI ())
169+ if (arity == 1 && EcorePackage . eNS_URI .equals (functionName .getNamespaceURI ())
181170 && "eClassName" .equals (functionName .getLocalPart ())) {
182171 return args -> {
183172 Node item = getSingleNodeArgument (args );
184- return EObjectContext .this .domProxy2object .get (item ).eClass ().getName ();
173+ EObject eObject = (EObject ) EObjectContext .this .domMapping .getValue (item );
174+ return eObject == null ? null : eObject .eClass ().getName ();
185175 };
186176 }
187177 return null ;
@@ -198,37 +188,60 @@ private static Node getSingleNodeArgument(List<?> args) throws XPathFunctionExce
198188 }
199189 throw new XPathFunctionException ("Not a single node list: " + args );
200190 }
191+ }
192+
193+ private static Element createElement (EObject eObject , Document document , DOMMapping domMapper ) {
194+ new XMLSaveImpl (Map .of (), new XMIHelperImpl (), "UTF-8" ).save (null , document ,
195+ Map .of (XMLResource .OPTION_ROOT_OBJECTS , List .of (eObject )), domMapper );
196+ return document .getDocumentElement ();
197+ }
198+
199+ private static NamespaceContext createNamespaceContext (Element element ) {
200+ element .setAttributeNS (XMLConstants .XMLNS_ATTRIBUTE_NS_URI , "xmlns:ecore" , EcorePackage .eNS_URI );
201+ final Map <String , String > xmlnsPrefixMap = new HashMap <>();
202+ final Map <String , String > xmlnsPrefixMapInverse = new HashMap <>();
203+
204+ NamedNodeMap attributes = element .getAttributes ();
205+ for (int i = 0 , length = attributes .getLength (); i < length ; ++i ) {
206+ Attr attribute = (Attr ) attributes .item (i );
207+ if ("xmlns" .equals (attribute .getPrefix ())) {
208+ String prefix = attribute .getLocalName ();
209+ String namespace = attribute .getValue ();
210+ xmlnsPrefixMap .put (prefix , namespace );
211+ xmlnsPrefixMapInverse .put (namespace , prefix );
212+ }
213+ }
214+
215+ return new NamespaceContext () {
216+ @ Override
217+ public String getNamespaceURI (String prefix ) {
218+ return xmlnsPrefixMap .get (prefix );
219+ }
201220
221+ @ Override
222+ public String getPrefix (String namespaceURI ) {
223+ return xmlnsPrefixMapInverse .get (namespaceURI );
224+ }
225+
226+ @ Override
227+ public Iterator <String > getPrefixes (String namespaceURI ) {
228+ String prefix = getPrefix (namespaceURI );
229+ List <String > list = prefix == null ? List .of () : List .of (prefix );
230+ return list .iterator ();
231+ }
232+ };
202233 }
203234
204- private static Element createElement (EObject eObject , Node parent ) {
205- EStructuralFeature containingFeature = eObject .eContainingFeature ();
206- EStructuralFeature containmentFeature = eObject .eContainmentFeature ();
207- String className = eObject .eClass ().getName ();
208- String qName = containingFeature != null ? containingFeature .getName () : className ;
209-
210- Document document = parent instanceof Document documentParent ? documentParent : parent .getOwnerDocument ();
211- Element element = document .createElement (qName );
212- EClass eClass = eObject .eClass ();
213- for (EAttribute attribute : eClass .getEAllAttributes ()) {
214- // TODO: or check how lists could be serialized as attributes? CSV? With
215- // leading/trailing square-bracket?
216- if (!attribute .isMany () && attribute .getEAttributeType ().isSerializable () && eObject .eIsSet (attribute )) {
217- Object value = eObject .eGet (attribute );
218- try {
219- String stringValue = EcoreUtil .convertToString (attribute .getEAttributeType (), value );
220- element .setAttribute (attribute .getName (), stringValue );
221- } catch (Exception e ) {
222- // TODO: avoid the occurrence of exceptions
235+ private static class DOMMapping extends DefaultDOMHandlerImpl {
236+
237+ public Element getElement (Object object ) {
238+ for (Map .Entry <Node , Object > entry : nodeToObject .entrySet ()) {
239+ if (Objects .equals (entry .getValue (), object )) {
240+ return (Element ) entry .getKey ();
223241 }
224242 }
243+ return null ;
225244 }
226- // TODO: set the xsi:type? This requires to declare the EPackage nsURI as xml
227- // namespace initially, but it's unsure to me if that's even necessary.
228- // String name = eClass.getName();
229- // childElement.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "xsi:type", name);
230-
231- parent .appendChild (element );
232- return element ;
233245 }
246+
234247}
0 commit comments