package org.argeo.cms.acr.xml;
+import java.nio.CharBuffer;
+import java.nio.file.Files;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentName;
-import org.argeo.api.acr.spi.AbstractContent;
+import org.argeo.api.acr.CrName;
import org.argeo.api.acr.spi.ProvidedContent;
import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.cms.acr.ContentUtils;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
+/** Content persisted as a DOM element. */
public class DomContent extends AbstractContent implements ProvidedContent {
- private final ProvidedSession session;
private final DomContentProvider provider;
private final Element element;
private Boolean hasText = null;
public DomContent(ProvidedSession session, DomContentProvider contentProvider, Element element) {
- this.session = session;
+ super(session);
this.provider = contentProvider;
this.element = element;
}
@Override
public QName getName() {
+ if (element.getParentNode() == null) {// root
+ String mountPath = provider.getMountPath();
+ if (mountPath != null) {
+ Content mountPoint = getSession().getMountPoint(mountPath);
+ return mountPoint.getName();
+ }
+ }
return toQName(this.element);
}
if (namespaceURI == null) {
return toQName(node, node.getLocalName());
} else {
- String contextPrefix = session.getPrefix(namespaceURI);
+ String contextPrefix = provider.getPrefix(namespaceURI);
if (contextPrefix == null)
throw new IllegalStateException("Namespace " + namespaceURI + " is unbound");
- return toQName(node, namespaceURI, node.getLocalName(), session);
+ return toQName(node, namespaceURI, node.getLocalName(), provider);
}
} else {
String namespaceURI = node.getNamespaceURI();
if (namespaceURI == null)
namespaceURI = node.getOwnerDocument().lookupNamespaceURI(prefix);
if (namespaceURI == null) {
- namespaceURI = session.getNamespaceURI(prefix);
+ namespaceURI = provider.getNamespaceURI(prefix);
if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
throw new IllegalStateException("Prefix " + prefix + " is unbound");
// TODO bind the prefix in the document?
}
- return toQName(node, namespaceURI, node.getLocalName(), session);
+ return toQName(node, namespaceURI, node.getLocalName(), provider);
}
}
protected QName toQName(Node source, String namespaceURI, String localName, NamespaceContext namespaceContext) {
- return new ContentName(namespaceURI, localName, session);
+ return new ContentName(namespaceURI, localName, namespaceContext);
}
protected QName toQName(Node source, String localName) {
return result;
}
+ @SuppressWarnings("unchecked")
@Override
public <A> Optional<A> get(QName key, Class<A> clss) {
String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
Object previous = get(key);
String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
: key.getNamespaceURI();
+ String prefixToUse = registerPrefixIfNeeded(key);
element.setAttributeNS(namespaceUriOrNull,
- namespaceUriOrNull == null ? key.getLocalPart() : key.getPrefix() + ":" + key.getLocalPart(),
+ namespaceUriOrNull == null ? key.getLocalPart() : prefixToUse + ":" + key.getLocalPart(),
value.toString());
return previous;
}
+ protected String registerPrefixIfNeeded(QName name) {
+ String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
+ : name.getNamespaceURI();
+ String prefixToUse;
+ if (namespaceUriOrNull != null) {
+ String registeredPrefix = provider.getPrefix(namespaceUriOrNull);
+ if (registeredPrefix != null) {
+ prefixToUse = registeredPrefix;
+ } else {
+ provider.registerPrefix(name.getPrefix(), namespaceUriOrNull);
+ prefixToUse = name.getPrefix();
+ }
+ } else {
+ prefixToUse = null;
+ }
+ return prefixToUse;
+ }
+
@Override
public boolean hasText() {
// return element instanceof Text;
@Override
public Iterator<Content> iterator() {
NodeList nodeList = element.getChildNodes();
- return new ElementIterator(this, session, provider, nodeList);
+ return new ElementIterator(this, getSession(), provider, nodeList);
}
@Override
public Content getParent() {
- Node parent = element.getParentNode();
- if (parent == null)
- return null;
- if (parent instanceof Document)
+ Node parentNode = element.getParentNode();
+ if (parentNode == null) {
+ String mountPath = provider.getMountPath();
+ if (mountPath == null)
+ return null;
+ String[] parent = ContentUtils.getParentPath(mountPath);
+ return getSession().get(parent[0]);
+ }
+ if (parentNode instanceof Document)
return null;
- if (!(parent instanceof Element))
+ if (!(parentNode instanceof Element))
throw new IllegalStateException("Parent is not an element");
- return new DomContent(this, (Element) parent);
+ return new DomContent(this, (Element) parentNode);
}
@Override
Document document = this.element.getOwnerDocument();
String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
: name.getNamespaceURI();
+ String prefixToUse = registerPrefixIfNeeded(name);
Element child = document.createElementNS(namespaceUriOrNull,
- namespaceUriOrNull == null ? name.getLocalPart() : name.getPrefix() + ":" + name.getLocalPart());
+ namespaceUriOrNull == null ? name.getLocalPart() : prefixToUse + ":" + name.getLocalPart());
element.appendChild(child);
return new DomContent(this, child);
}
}
- public ProvidedSession getSession() {
- return session;
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A> A adapt(Class<A> clss) throws IllegalArgumentException {
+ if (CharBuffer.class.isAssignableFrom(clss)) {
+ String textContent = element.getTextContent();
+ CharBuffer buf = CharBuffer.wrap(textContent);
+ return (A) buf;
+ }
+ return super.adapt(clss);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <A> CompletableFuture<A> write(Class<A> clss) {
+ if (String.class.isAssignableFrom(clss)) {
+ CompletableFuture<String> res = new CompletableFuture<>();
+ res.thenAccept((s) -> {
+ getSession().notifyModification(this);
+ element.setTextContent(s);
+ });
+ return (CompletableFuture<A>) res;
+ }
+ return super.write(clss);
+ }
+
+ /*
+ * TYPING
+ */
+ @Override
+ public List<QName> getContentClasses() {
+ List<QName> res = new ArrayList<>();
+ res.add(getName());
+ return res;
+ }
+
+ /*
+ * MOUNT MANAGEMENT
+ */
+ @Override
+ public ProvidedContent getMountPoint(String relativePath) {
+ // FIXME use qualified names
+ Element childElement = (Element) element.getElementsByTagName(relativePath).item(0);
+ // TODO check that it is a mount
+ return new DomContent(this, childElement);
}
+ @Override
public DomContentProvider getProvider() {
return provider;
}