1 package org
.argeo
.cms
.acr
.xml
;
3 import java
.nio
.CharBuffer
;
4 import java
.nio
.file
.Files
;
5 import java
.util
.ArrayList
;
6 import java
.util
.HashSet
;
7 import java
.util
.Iterator
;
9 import java
.util
.Optional
;
11 import java
.util
.concurrent
.CompletableFuture
;
13 import javax
.xml
.XMLConstants
;
14 import javax
.xml
.namespace
.NamespaceContext
;
15 import javax
.xml
.namespace
.QName
;
17 import org
.argeo
.api
.acr
.Content
;
18 import org
.argeo
.api
.acr
.ContentName
;
19 import org
.argeo
.api
.acr
.CrName
;
20 import org
.argeo
.api
.acr
.spi
.ProvidedContent
;
21 import org
.argeo
.api
.acr
.spi
.ProvidedSession
;
22 import org
.argeo
.cms
.acr
.AbstractContent
;
23 import org
.argeo
.cms
.acr
.ContentUtils
;
24 import org
.w3c
.dom
.Attr
;
25 import org
.w3c
.dom
.Document
;
26 import org
.w3c
.dom
.Element
;
27 import org
.w3c
.dom
.NamedNodeMap
;
28 import org
.w3c
.dom
.Node
;
29 import org
.w3c
.dom
.NodeList
;
30 import org
.w3c
.dom
.Text
;
32 /** Content persisted as a DOM element. */
33 public class DomContent
extends AbstractContent
implements ProvidedContent
{
35 private final DomContentProvider provider
;
36 private final Element element
;
38 // private String text = null;
39 private Boolean hasText
= null;
41 public DomContent(ProvidedSession session
, DomContentProvider contentProvider
, Element element
) {
43 this.provider
= contentProvider
;
44 this.element
= element
;
47 public DomContent(DomContent context
, Element element
) {
48 this(context
.getSession(), context
.getProvider(), element
);
52 public QName
getName() {
53 if (element
.getParentNode() == null) {// root
54 String mountPath
= provider
.getMountPath();
55 if (mountPath
!= null) {
56 Content mountPoint
= getSession().getMountPoint(mountPath
);
57 return mountPoint
.getName();
60 return toQName(this.element
);
63 protected QName
toQName(Node node
) {
64 String prefix
= node
.getPrefix();
66 String namespaceURI
= node
.getNamespaceURI();
67 if (namespaceURI
== null)
68 namespaceURI
= node
.getOwnerDocument().lookupNamespaceURI(null);
69 if (namespaceURI
== null) {
70 return toQName(node
, node
.getLocalName());
72 String contextPrefix
= provider
.getPrefix(namespaceURI
);
73 if (contextPrefix
== null)
74 throw new IllegalStateException("Namespace " + namespaceURI
+ " is unbound");
75 return toQName(node
, namespaceURI
, node
.getLocalName(), provider
);
78 String namespaceURI
= node
.getNamespaceURI();
79 if (namespaceURI
== null)
80 namespaceURI
= node
.getOwnerDocument().lookupNamespaceURI(prefix
);
81 if (namespaceURI
== null) {
82 namespaceURI
= provider
.getNamespaceURI(prefix
);
83 if (XMLConstants
.NULL_NS_URI
.equals(namespaceURI
))
84 throw new IllegalStateException("Prefix " + prefix
+ " is unbound");
85 // TODO bind the prefix in the document?
87 return toQName(node
, namespaceURI
, node
.getLocalName(), provider
);
91 protected QName
toQName(Node source
, String namespaceURI
, String localName
, NamespaceContext namespaceContext
) {
92 return new ContentName(namespaceURI
, localName
, namespaceContext
);
95 protected QName
toQName(Node source
, String localName
) {
96 return new ContentName(localName
);
99 * ATTRIBUTES OPERATIONS
103 public Iterable
<QName
> keys() {
104 // TODO implement an iterator?
105 Set
<QName
> result
= new HashSet
<>();
106 NamedNodeMap attributes
= element
.getAttributes();
107 for (int i
= 0; i
< attributes
.getLength(); i
++) {
108 Attr attr
= (Attr
) attributes
.item(i
);
109 QName key
= toQName(attr
);
115 @SuppressWarnings("unchecked")
117 public <A
> Optional
<A
> get(QName key
, Class
<A
> clss
) {
118 String namespaceUriOrNull
= XMLConstants
.NULL_NS_URI
.equals(key
.getNamespaceURI()) ?
null
119 : key
.getNamespaceURI();
120 if (element
.hasAttributeNS(namespaceUriOrNull
, key
.getLocalPart())) {
121 String value
= element
.getAttributeNS(namespaceUriOrNull
, key
.getLocalPart());
122 if (clss
.isAssignableFrom(String
.class))
123 return Optional
.of((A
) value
);
125 return Optional
.empty();
131 public Object
put(QName key
, Object value
) {
132 Object previous
= get(key
);
133 String namespaceUriOrNull
= XMLConstants
.NULL_NS_URI
.equals(key
.getNamespaceURI()) ?
null
134 : key
.getNamespaceURI();
135 String prefixToUse
= registerPrefixIfNeeded(key
);
136 element
.setAttributeNS(namespaceUriOrNull
,
137 namespaceUriOrNull
== null ? key
.getLocalPart() : prefixToUse
+ ":" + key
.getLocalPart(),
142 protected String
registerPrefixIfNeeded(QName name
) {
143 String namespaceUriOrNull
= XMLConstants
.NULL_NS_URI
.equals(name
.getNamespaceURI()) ?
null
144 : name
.getNamespaceURI();
146 if (namespaceUriOrNull
!= null) {
147 String registeredPrefix
= provider
.getPrefix(namespaceUriOrNull
);
148 if (registeredPrefix
!= null) {
149 prefixToUse
= registeredPrefix
;
151 provider
.registerPrefix(name
.getPrefix(), namespaceUriOrNull
);
152 prefixToUse
= name
.getPrefix();
161 public boolean hasText() {
162 // return element instanceof Text;
165 NodeList nodeList
= element
.getChildNodes();
166 if (nodeList
.getLength() > 1) {
170 nodes
: for (int i
= 0; i
< nodeList
.getLength(); i
++) {
171 Node node
= nodeList
.item(i
);
172 if (node
instanceof Text
) {
173 Text text
= (Text
) node
;
174 if (!text
.isElementContentWhitespace()) {
185 // text = element.getTextContent();
186 // return text != null;
190 public String
getText() {
192 return element
.getTextContent();
202 public Iterator
<Content
> iterator() {
203 NodeList nodeList
= element
.getChildNodes();
204 return new ElementIterator(this, getSession(), provider
, nodeList
);
208 public Content
getParent() {
209 Node parentNode
= element
.getParentNode();
210 if (parentNode
== null) {
211 String mountPath
= provider
.getMountPath();
212 if (mountPath
== null)
214 String
[] parent
= ContentUtils
.getParentPath(mountPath
);
215 return getSession().get(parent
[0]);
217 if (parentNode
instanceof Document
)
219 if (!(parentNode
instanceof Element
))
220 throw new IllegalStateException("Parent is not an element");
221 return new DomContent(this, (Element
) parentNode
);
225 public Content
add(QName name
, QName
... classes
) {
226 // TODO consider classes
227 Document document
= this.element
.getOwnerDocument();
228 String namespaceUriOrNull
= XMLConstants
.NULL_NS_URI
.equals(name
.getNamespaceURI()) ?
null
229 : name
.getNamespaceURI();
230 String prefixToUse
= registerPrefixIfNeeded(name
);
231 Element child
= document
.createElementNS(namespaceUriOrNull
,
232 namespaceUriOrNull
== null ? name
.getLocalPart() : prefixToUse
+ ":" + name
.getLocalPart());
233 element
.appendChild(child
);
234 return new DomContent(this, child
);
238 public void remove() {
239 // TODO make it more robust
240 element
.getParentNode().removeChild(element
);
245 protected void removeAttr(QName key
) {
246 String namespaceUriOrNull
= XMLConstants
.NULL_NS_URI
.equals(key
.getNamespaceURI()) ?
null
247 : key
.getNamespaceURI();
248 element
.removeAttributeNS(namespaceUriOrNull
,
249 namespaceUriOrNull
== null ? key
.getLocalPart() : key
.getPrefix() + ":" + key
.getLocalPart());
253 @SuppressWarnings("unchecked")
255 public <A
> A
adapt(Class
<A
> clss
) throws IllegalArgumentException
{
256 if (CharBuffer
.class.isAssignableFrom(clss
)) {
257 String textContent
= element
.getTextContent();
258 CharBuffer buf
= CharBuffer
.wrap(textContent
);
261 return super.adapt(clss
);
264 @SuppressWarnings("unchecked")
265 public <A
> CompletableFuture
<A
> write(Class
<A
> clss
) {
266 if (String
.class.isAssignableFrom(clss
)) {
267 CompletableFuture
<String
> res
= new CompletableFuture
<>();
268 res
.thenAccept((s
) -> {
269 getSession().notifyModification(this);
270 element
.setTextContent(s
);
272 return (CompletableFuture
<A
>) res
;
274 return super.write(clss
);
281 public List
<QName
> getContentClasses() {
282 List
<QName
> res
= new ArrayList
<>();
291 public ProvidedContent
getMountPoint(String relativePath
) {
292 // FIXME use qualified names
293 Element childElement
= (Element
) element
.getElementsByTagName(relativePath
).item(0);
294 // TODO check that it is a mount
295 return new DomContent(this, childElement
);
299 public DomContentProvider
getProvider() {