]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java
Use runtime namespace context as default.
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / acr / xml / DomContent.java
1 package org.argeo.cms.acr.xml;
2
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;
8 import java.util.List;
9 import java.util.Optional;
10 import java.util.Set;
11 import java.util.concurrent.CompletableFuture;
12
13 import javax.xml.XMLConstants;
14 import javax.xml.namespace.NamespaceContext;
15 import javax.xml.namespace.QName;
16
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;
31
32 /** Content persisted as a DOM element. */
33 public class DomContent extends AbstractContent implements ProvidedContent {
34
35 private final DomContentProvider provider;
36 private final Element element;
37
38 // private String text = null;
39 private Boolean hasText = null;
40
41 public DomContent(ProvidedSession session, DomContentProvider contentProvider, Element element) {
42 super(session);
43 this.provider = contentProvider;
44 this.element = element;
45 }
46
47 public DomContent(DomContent context, Element element) {
48 this(context.getSession(), context.getProvider(), element);
49 }
50
51 @Override
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();
58 }
59 }
60 return toQName(this.element);
61 }
62
63 protected QName toQName(Node node) {
64 String prefix = node.getPrefix();
65 if (prefix == null) {
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());
71 } else {
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);
76 }
77 } else {
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?
86 }
87 return toQName(node, namespaceURI, node.getLocalName(), provider);
88 }
89 }
90
91 protected QName toQName(Node source, String namespaceURI, String localName, NamespaceContext namespaceContext) {
92 return new ContentName(namespaceURI, localName, namespaceContext);
93 }
94
95 protected QName toQName(Node source, String localName) {
96 return new ContentName(localName);
97 }
98 /*
99 * ATTRIBUTES OPERATIONS
100 */
101
102 @Override
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);
110 result.add(key);
111 }
112 return result;
113 }
114
115 @SuppressWarnings("unchecked")
116 @Override
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);
124 else
125 return Optional.empty();
126 } else
127 return null;
128 }
129
130 @Override
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(),
138 value.toString());
139 return previous;
140 }
141
142 protected String registerPrefixIfNeeded(QName name) {
143 String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
144 : name.getNamespaceURI();
145 String prefixToUse;
146 if (namespaceUriOrNull != null) {
147 String registeredPrefix = provider.getPrefix(namespaceUriOrNull);
148 if (registeredPrefix != null) {
149 prefixToUse = registeredPrefix;
150 } else {
151 provider.registerPrefix(name.getPrefix(), namespaceUriOrNull);
152 prefixToUse = name.getPrefix();
153 }
154 } else {
155 prefixToUse = null;
156 }
157 return prefixToUse;
158 }
159
160 @Override
161 public boolean hasText() {
162 // return element instanceof Text;
163 if (hasText != null)
164 return hasText;
165 NodeList nodeList = element.getChildNodes();
166 if (nodeList.getLength() > 1) {
167 hasText = false;
168 return hasText;
169 }
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()) {
175 hasText = true;
176 break nodes;
177 }
178 }
179 }
180 if (hasText == null)
181 hasText = false;
182 return hasText;
183 // if (text != null)
184 // return true;
185 // text = element.getTextContent();
186 // return text != null;
187 }
188
189 @Override
190 public String getText() {
191 if (hasText())
192 return element.getTextContent();
193 else
194 return null;
195 }
196
197 /*
198 * CONTENT OPERATIONS
199 */
200
201 @Override
202 public Iterator<Content> iterator() {
203 NodeList nodeList = element.getChildNodes();
204 return new ElementIterator(this, getSession(), provider, nodeList);
205 }
206
207 @Override
208 public Content getParent() {
209 Node parentNode = element.getParentNode();
210 if (parentNode == null) {
211 String mountPath = provider.getMountPath();
212 if (mountPath == null)
213 return null;
214 String[] parent = ContentUtils.getParentPath(mountPath);
215 return getSession().get(parent[0]);
216 }
217 if (parentNode instanceof Document)
218 return null;
219 if (!(parentNode instanceof Element))
220 throw new IllegalStateException("Parent is not an element");
221 return new DomContent(this, (Element) parentNode);
222 }
223
224 @Override
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);
235 }
236
237 @Override
238 public void remove() {
239 // TODO make it more robust
240 element.getParentNode().removeChild(element);
241
242 }
243
244 @Override
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());
250
251 }
252
253 @SuppressWarnings("unchecked")
254 @Override
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);
259 return (A) buf;
260 }
261 return super.adapt(clss);
262 }
263
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);
271 });
272 return (CompletableFuture<A>) res;
273 }
274 return super.write(clss);
275 }
276
277 /*
278 * TYPING
279 */
280 @Override
281 public List<QName> getContentClasses() {
282 List<QName> res = new ArrayList<>();
283 res.add(getName());
284 return res;
285 }
286
287 /*
288 * MOUNT MANAGEMENT
289 */
290 @Override
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);
296 }
297
298 @Override
299 public DomContentProvider getProvider() {
300 return provider;
301 }
302
303 }