X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=org.argeo.cms%2Fsrc%2Forg%2Fargeo%2Fcms%2Facr%2Fxml%2FDomContent.java;h=30ecd8e82b649be7251473c204062937f5a3af8c;hb=c0342975a37c70895c2e8f6b341d790700168d7f;hp=5cb4583624c59fc41dd17956ab5ec355013a0cdf;hpb=c615307d7b87bcb260d8a9f402c6e0a880862f38;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java index 5cb458362..30ecd8e82 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java @@ -1,30 +1,42 @@ package org.argeo.cms.acr.xml; +import java.nio.CharBuffer; +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 javax.xml.namespace.QName; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentName; -import org.argeo.api.acr.spi.AbstractContent; 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.DOMException; import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; 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; @@ -32,7 +44,7 @@ public class DomContent extends AbstractContent implements ProvidedContent { private Boolean hasText = null; public DomContent(ProvidedSession session, DomContentProvider contentProvider, Element element) { - this.session = session; + super(session); this.provider = contentProvider; this.element = element; } @@ -43,6 +55,13 @@ public class DomContent extends AbstractContent implements ProvidedContent { @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); } @@ -55,27 +74,27 @@ public class DomContent extends AbstractContent implements ProvidedContent { 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) { @@ -98,6 +117,7 @@ public class DomContent extends AbstractContent implements ProvidedContent { return result; } + @SuppressWarnings("unchecked") @Override public Optional get(QName key, Class clss) { String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null @@ -117,12 +137,31 @@ public class DomContent extends AbstractContent implements ProvidedContent { 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; @@ -167,19 +206,24 @@ public class DomContent extends AbstractContent implements ProvidedContent { @Override public Iterator 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 @@ -188,8 +232,9 @@ public class DomContent extends AbstractContent implements ProvidedContent { 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); } @@ -210,10 +255,91 @@ public class DomContent extends AbstractContent implements ProvidedContent { } - public ProvidedSession getSession() { - return session; + @SuppressWarnings("unchecked") + @Override + public A adapt(Class clss) throws IllegalArgumentException { + if (CharBuffer.class.isAssignableFrom(clss)) { + String textContent = element.getTextContent(); + CharBuffer buf = CharBuffer.wrap(textContent); + return (A) buf; + } else if (Source.class.isAssignableFrom(clss)) { + DOMSource source = new DOMSource(element); + return (A) source; + } + return super.adapt(clss); } + @SuppressWarnings("unchecked") + public CompletableFuture write(Class clss) { + if (String.class.isAssignableFrom(clss)) { + CompletableFuture res = new CompletableFuture<>(); + res.thenAccept((s) -> { + getSession().notifyModification(this); + element.setTextContent(s); + }); + return (CompletableFuture) res; + } else if (Source.class.isAssignableFrom(clss)) { + CompletableFuture res = new CompletableFuture<>(); + res.thenAccept((source) -> { + try { + Transformer transformer = provider.getTransformerFactory().newTransformer(); + DocumentFragment documentFragment = element.getOwnerDocument().createDocumentFragment(); + DOMResult result = new DOMResult(documentFragment); + transformer.transform(source, result); + // Node parentNode = element.getParentNode(); + Element resultElement = (Element) documentFragment.getFirstChild(); + QName resultName = toQName(resultElement); + if (!resultName.equals(getName())) + throw new IllegalArgumentException(resultName + "+ is not compatible with " + getName()); + + // attributes + NamedNodeMap attrs = resultElement.getAttributes(); + for (int i = 0; i < attrs.getLength(); i++) { + Attr attr2 = (Attr) element.getOwnerDocument().importNode(attrs.item(i), true); + element.getAttributes().setNamedItem(attr2); + } + + // Move all the children + while (element.hasChildNodes()) { + element.removeChild(element.getFirstChild()); + } + while (resultElement.hasChildNodes()) { + element.appendChild(resultElement.getFirstChild()); + } +// parentNode.replaceChild(resultNode, element); +// element = (Element)resultNode; + + } catch (DOMException | TransformerException e) { + throw new RuntimeException("Cannot write to element", e); + } + }); + return (CompletableFuture) res; + } + return super.write(clss); + } + + /* + * TYPING + */ + @Override + public List getContentClasses() { + List 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; }