--- /dev/null
+package org.argeo.app.jcr.docbook;
+
+import static org.argeo.app.docbook.DbkType.para;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.docbook.DbkAttr;
+import org.argeo.app.docbook.DbkType;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.jcr.JcrxApi;
+
+/** JCR utilities around DocBook. */
+public class DbkJcrUtils {
+ private final static CmsLog log = CmsLog.getLog(DbkJcrUtils.class);
+
+ /** Get or add a DocBook element. */
+ public static Node getOrAddDbk(Node parent, DbkType child) {
+ try {
+ if (!parent.hasNode(child.get())) {
+ return addDbk(parent, child);
+ } else {
+ return parent.getNode(child.get());
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get or add element " + child.get() + " to " + parent, e);
+ }
+ }
+
+ /** Add a DocBook element to this node. */
+ public static Node addDbk(Node parent, DbkType child) {
+ try {
+ Node node = parent.addNode(child.get(), child.get());
+ return node;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot add element " + child.get() + " to " + parent, e);
+ }
+ }
+
+ /** Whether this DocBook element is of this type. */
+ public static boolean isDbk(Node node, DbkType type) {
+ return Jcr.getName(node).equals(type.get());
+ }
+
+ /** Whether this node is a DocBook type. */
+ public static boolean isDbk(Node node) {
+ String name = Jcr.getName(node);
+ for (DbkType type : DbkType.values()) {
+ if (name.equals(type.get()))
+ return true;
+ }
+ return false;
+ }
+
+ public static String getTitle(Node node) {
+ return JcrxApi.getXmlValue(node, DbkType.title.get());
+ }
+
+ public static void setTitle(Node node, String txt) {
+ Node titleNode = getOrAddDbk(node, DbkType.title);
+ JcrxApi.setXmlValue(titleNode, txt);
+ }
+
+ public static Node getMetadata(Node infoContainer) {
+ try {
+ if (!infoContainer.hasNode(DbkType.info.get()))
+ return null;
+ Node info = infoContainer.getNode(DbkType.info.get());
+ if (!info.hasNode(EntityType.local.get()))
+ return null;
+ return info.getNode(EntityType.local.get());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrieve metadata from " + infoContainer, e);
+ }
+ }
+
+ public static Node getChildByRole(Node parent, String role) {
+ try {
+ NodeIterator baseSections = parent.getNodes();
+ while (baseSections.hasNext()) {
+ Node n = baseSections.nextNode();
+ String r = Jcr.get(n, DbkAttr.role.name());
+ if (r != null && r.equals(role))
+ return n;
+ }
+ return null;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get child from " + parent + " with role " + role, e);
+ }
+ }
+
+ public static Node addParagraph(Node node, String txt) {
+ Node p = addDbk(node, para);
+ JcrxApi.setXmlValue(p, txt);
+ return p;
+ }
+
+ /**
+ * Removes a paragraph if it empty. The sesison is not saved.
+ *
+ * @return true if the paragraph was empty and it was removed
+ */
+ public static boolean removeIfEmptyParagraph(Node node) {
+ try {
+ if (isDbk(node, DbkType.para)) {
+ NodeIterator nit = node.getNodes();
+ if (!nit.hasNext()) {
+ node.remove();
+ return true;
+ }
+ Node first = nit.nextNode();
+ if (nit.hasNext())
+ return false;
+ if (first.getName().equals(Jcr.JCR_XMLTEXT)) {
+ String str = Jcr.get(first, Jcr.JCR_XMLCHARACTERS);
+ if (str != null && str.trim().equals("")) {
+ node.remove();
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+ return false;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot remove possibly empty paragraph", e);
+ }
+ }
+
+ public static Node insertImageAfter(Node sibling) {
+ try {
+
+ Node parent = sibling.getParent();
+ Node mediaNode = addDbk(parent, DbkType.mediaobject);
+ // TODO optimise?
+ parent.orderBefore(mediaNode.getName() + "[" + mediaNode.getIndex() + "]",
+ sibling.getName() + "[" + sibling.getIndex() + "]");
+ parent.orderBefore(sibling.getName() + "[" + sibling.getIndex() + "]",
+ mediaNode.getName() + "[" + mediaNode.getIndex() + "]");
+
+ Node imageNode = addDbk(mediaNode, DbkType.imageobject);
+ Node imageDataNode = addDbk(imageNode, DbkType.imagedata);
+// Node infoNode = imageNode.addNode(DocBookTypes.INFO, DocBookTypes.INFO);
+// Node fileNode = JcrUtils.copyBytesAsFile(mediaFolder, EntityType.box.get(), new byte[0]);
+// fileNode.addMixin(EntityType.box.get());
+// fileNode.setProperty(EntityNames.SVG_WIDTH, 0);
+// fileNode.setProperty(EntityNames.SVG_LENGTH, 0);
+// fileNode.addMixin(NodeType.MIX_MIMETYPE);
+//
+// // we assume this is a folder next to the main DocBook document
+// // TODO make it more robust and generic
+// String fileRef = mediaNode.getName();
+// imageDataNode.setProperty(DocBookNames.DBK_FILEREF, fileRef);
+ return mediaNode;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot insert empty image after " + sibling, e);
+ }
+ }
+
+ public static Node insertVideoAfter(Node sibling) {
+ try {
+
+ Node parent = sibling.getParent();
+ Node mediaNode = addDbk(parent, DbkType.mediaobject);
+ // TODO optimise?
+ parent.orderBefore(mediaNode.getName() + "[" + mediaNode.getIndex() + "]",
+ sibling.getName() + "[" + sibling.getIndex() + "]");
+ parent.orderBefore(sibling.getName() + "[" + sibling.getIndex() + "]",
+ mediaNode.getName() + "[" + mediaNode.getIndex() + "]");
+
+ Node videoNode = addDbk(mediaNode, DbkType.videoobject);
+ Node videoDataNode = addDbk(videoNode, DbkType.videodata);
+ return mediaNode;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot insert empty image after " + sibling, e);
+ }
+ }
+
+ public static String getMediaFileref(Node node) {
+ try {
+ Node mediadata;
+ if (node.hasNode(DbkType.imageobject.get())) {
+ mediadata = node.getNode(DbkType.imageobject.get()).getNode(DbkType.imagedata.get());
+ } else if (node.hasNode(DbkType.videoobject.get())) {
+ mediadata = node.getNode(DbkType.videoobject.get()).getNode(DbkType.videodata.get());
+ } else {
+ throw new IllegalArgumentException("Fileref not found in " + node);
+ }
+
+ if (mediadata.hasProperty(DbkAttr.fileref.name())) {
+ return mediadata.getProperty(DbkAttr.fileref.name()).getString();
+ } else {
+ return null;
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrieve file ref from " + node, e);
+ }
+ }
+
+ public static void exportXml(Node node, OutputStream out) throws IOException {
+ try {
+ node.getSession().exportDocumentView(node.getPath(), out, false, false);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot export " + node + " to XML", e);
+ }
+ }
+
+ public static void exportToFs(Node baseNode, DbkType type, Path directory) {
+ String fileName = Jcr.getName(baseNode) + ".dbk.xml";
+ Path filePath = directory.resolve(fileName);
+ Node docBookNode = Jcr.getNode(baseNode, type.get());
+ if (docBookNode == null)
+ throw new IllegalArgumentException("No " + type.get() + " under " + baseNode);
+ try {
+ Files.createDirectories(directory);
+ try (OutputStream out = Files.newOutputStream(filePath)) {
+ exportXml(docBookNode, out);
+ }
+ JcrUtils.copyFilesToFs(baseNode, directory, true);
+ if (log.isDebugEnabled())
+ log.debug("DocBook " + baseNode + " exported to " + filePath.toAbsolutePath());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void importXml(Node baseNode, InputStream in) throws IOException {
+ try {
+ baseNode.getSession().importXML(baseNode.getPath(), in,
+ ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot import XML to " + baseNode, e);
+ }
+
+ }
+
+ /** Singleton. */
+ private DbkJcrUtils() {
+ }
+
+}