Extract JCR in a separate bundle
authorMathieu Baudier <mbaudier@argeo.org>
Tue, 13 Jun 2023 08:28:44 +0000 (10:28 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Tue, 13 Jun 2023 08:28:44 +0000 (10:28 +0200)
72 files changed:
Makefile
org.argeo.app.core/OSGI-INF/appUserState.xml [deleted file]
org.argeo.app.core/OSGI-INF/l10n/bundle.properties
org.argeo.app.core/OSGI-INF/maintenanceService.xml [deleted file]
org.argeo.app.core/OSGI-INF/suiteMaintenance.xml [new file with mode: 0644]
org.argeo.app.core/OSGI-INF/termsManager.xml [deleted file]
org.argeo.app.core/bnd.bnd
org.argeo.app.core/build.properties
org.argeo.app.core/src/org/argeo/app/core/CustomMaintenanceService.java [deleted file]
org.argeo.app.core/src/org/argeo/app/core/JcrEntityDefinition.java [deleted file]
org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenance.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenanceService.java [deleted file]
org.argeo.app.core/src/org/argeo/app/core/SuiteTerm.java [deleted file]
org.argeo.app.core/src/org/argeo/app/core/SuiteTermsManager.java [deleted file]
org.argeo.app.core/src/org/argeo/app/core/SuiteTypology.java [deleted file]
org.argeo.app.core/src/org/argeo/app/core/SuiteUtils.java
org.argeo.app.core/src/org/argeo/app/core/XPathUtils.java [deleted file]
org.argeo.app.core/src/org/argeo/app/docbook/Dbk4Converter.java [deleted file]
org.argeo.app.core/src/org/argeo/app/docbook/DbkUtils.java [deleted file]
org.argeo.app.core/src/org/argeo/app/docbook/db4-upgrade.xsl [deleted file]
org.argeo.app.core/src/org/argeo/app/odk/OdkUtils.java [deleted file]
org.argeo.app.core/src/org/argeo/app/xforms/FormSubmissionListener.java
org.argeo.app.core/src/org/argeo/internal/app/core/AppUserStateImpl.java [deleted file]
org.argeo.app.jcr/.classpath [new file with mode: 0644]
org.argeo.app.jcr/.project [new file with mode: 0644]
org.argeo.app.jcr/OSGI-INF/appUserState.xml [new file with mode: 0644]
org.argeo.app.jcr/OSGI-INF/maintenanceService.xml [new file with mode: 0644]
org.argeo.app.jcr/OSGI-INF/termsManager.xml [new file with mode: 0644]
org.argeo.app.jcr/bnd.bnd [new file with mode: 0644]
org.argeo.app.jcr/build.properties [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/CustomMaintenanceService.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/JcrEntityDefinition.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/SuiteJcrUtils.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/XPathUtils.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/Dbk4Converter.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/DbkJcrUtils.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/db4-upgrade.xsl [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/odk/OdkJcrUtils.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTerm.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTermsManager.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTypology.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/internal/app/jcr/AppUserStateImpl.java [new file with mode: 0644]
org.argeo.app.jcr/src/org/argeo/internal/app/jcr/SuiteMaintenanceService.java [new file with mode: 0644]
org.argeo.app.servlet.odk/src/org/argeo/app/servlet/odk/OdkSubmissionServlet.java
org.argeo.app.servlet.publish/src/org/argeo/app/servlet/publish/DbkServlet.java
org.argeo.product.knowledge/.classpath [new file with mode: 0644]
org.argeo.product.knowledge/.project [new file with mode: 0644]
org.argeo.product.knowledge/OSGI-INF/l10n/bundle.properties [new file with mode: 0644]
org.argeo.product.knowledge/OSGI-INF/swtArgeoApp.xml [new file with mode: 0644]
org.argeo.product.knowledge/OSGI-INF/termsEntryArea.xml [new file with mode: 0644]
org.argeo.product.knowledge/OSGI-INF/termsLayer.xml [new file with mode: 0644]
org.argeo.product.knowledge/bnd.bnd [new file with mode: 0644]
org.argeo.product.knowledge/build.properties [new file with mode: 0644]
org.argeo.product.knowledge/config/swtArgeoApp.properties [new file with mode: 0644]
org.argeo.product.knowledge/config/termsEntryArea.properties [new file with mode: 0644]
org.argeo.product.knowledge/config/termsLayer.properties [new file with mode: 0644]
sdk/argeo-suite-server.properties
swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/TermsEntryArea.java [new file with mode: 0644]
swt/org.argeo.app.ui/OSGI-INF/l10n/bundle.properties
swt/org.argeo.app.ui/OSGI-INF/termsEntryArea.xml [deleted file]
swt/org.argeo.app.ui/OSGI-INF/termsLayer.xml [deleted file]
swt/org.argeo.app.ui/bnd.bnd
swt/org.argeo.app.ui/config/termsEntryArea.properties [deleted file]
swt/org.argeo.app.ui/config/termsLayer.properties [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/RecentItems.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/TermsEntryArea.java [deleted file]
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/AbstractDbkViewer.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkContextMenu.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkImageManager.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkTextInterpreter.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DbkVideo.java
swt/org.argeo.app.ui/src/org/argeo/app/ui/docbook/DocumentTextEditor.java

index b082fcf0ec9efb9ee3fb37b78d6cf1fcfb45fa24..4f2a4146cf5dda6f8e74a63d191edd746558dcb5 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -12,6 +12,7 @@ A2_CATEGORY = org.argeo.suite
 BUNDLES = \
 org.argeo.app.api \
 org.argeo.app.core \
+org.argeo.app.jcr \
 org.argeo.app.servlet.odk \
 org.argeo.app.servlet.publish \
 org.argeo.app.theme.default \
@@ -19,6 +20,7 @@ org.argeo.app.profile.acr.fs \
 org.argeo.app.profile.acr.jcr \
 swt/org.argeo.app.swt \
 swt/org.argeo.app.ui \
+org.argeo.product.knowledge \
 
 DEP_CATEGORIES = \
 org.argeo.tp \
diff --git a/org.argeo.app.core/OSGI-INF/appUserState.xml b/org.argeo.app.core/OSGI-INF/appUserState.xml
deleted file mode 100644 (file)
index 82c3ec0..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.internal.app.core.appUserState">
-   <implementation class="org.argeo.internal.app.core.AppUserStateImpl"/>
-   <service>
-      <provide interface="org.argeo.app.api.AppUserState"/>
-   </service>
-   <reference bind="setJcrContentProvider" cardinality="1..1" interface="org.argeo.cms.jcr.acr.JcrContentProvider" name="JcrContentProvider" policy="static"/>
-</scr:component>
index d4bf08ada4aa90cfcb452176066f239f901393c7..2cb3bb20ba1e6a2f6944db5a885e884ce27d7615 100644 (file)
@@ -4,8 +4,6 @@ documents=documents
 locations=locations
 recentItems=recent items
 
-appTitle=Argeo Suite
-
 #
 # PEOPLE
 # org.argeo.people.ui.PeopleMsg
diff --git a/org.argeo.app.core/OSGI-INF/maintenanceService.xml b/org.argeo.app.core/OSGI-INF/maintenanceService.xml
deleted file mode 100644 (file)
index 965d82b..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="true"  name="Suite Maintenance Service">
-   <implementation class="org.argeo.app.core.SuiteMaintenanceService"/>
-   <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static" target="(cn=ego)"/>
-   <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.api.cms.transaction.WorkTransaction" name="WorkTransaction" policy="static"/>
-   <reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
-   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.spi.ProvidedRepository" name="ContentRepository" policy="static"/>
-</scr:component>
diff --git a/org.argeo.app.core/OSGI-INF/suiteMaintenance.xml b/org.argeo.app.core/OSGI-INF/suiteMaintenance.xml
new file mode 100644 (file)
index 0000000..d06afa6
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="org.argeo.app.core.suiteMaintenance">
+   <implementation class="org.argeo.app.core.SuiteMaintenance"/>
+   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.spi.ProvidedRepository" name="ContentRepository" policy="static"/>
+</scr:component>
diff --git a/org.argeo.app.core/OSGI-INF/termsManager.xml b/org.argeo.app.core/OSGI-INF/termsManager.xml
deleted file mode 100644 (file)
index 797c5a3..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="true" name="Suite Terms Manager">
-   <implementation class="org.argeo.app.core.SuiteTermsManager"/>
-   <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static" target="(cn=ego)"/>
-   <service>
-      <provide interface="org.argeo.app.api.TermsManager"/>
-   </service>
-</scr:component>
index dce57724e96ae28467d4c4f14b95b33f120fa0f8..7a92b9b2d68c2505467145055966d034099d2570 100644 (file)
@@ -1,17 +1,12 @@
 Bundle-ActivationPolicy: lazy
 
 Service-Component:\
-OSGI-INF/termsManager.xml,\
-OSGI-INF/maintenanceService.xml,\
-OSGI-INF/appUserState.xml,\
+OSGI-INF/suiteMaintenance.xml,\
 
 Import-Package:\
 tech.units.indriya.unit,\
 org.osgi.service.useradmin,\
-javax.jcr.nodetype,\
-javax.jcr.security,\
 com.fasterxml.jackson.core,\
-org.apache.jackrabbit.*;version="[1,4)",\
 org.argeo.cms.acr,\
 *
 
index dc82853c4ceed6a8b1252e71cc26c98ee0f32a9d..4974d56f40c2ca9efc6f87e80f72414fb1d0a90a 100644 (file)
@@ -1,7 +1,8 @@
 bin.includes = META-INF/,\
                .,\
                OSGI-INF/,\
-               OSGI-INF/appUserState.xml
+               OSGI-INF/appUserState.xml,\
+               OSGI-INF/suiteMaintenance.xml
 additional.bundles = org.argeo.init
 source.. = src/
 output.. = bin/
diff --git a/org.argeo.app.core/src/org/argeo/app/core/CustomMaintenanceService.java b/org.argeo.app.core/src/org/argeo/app/core/CustomMaintenanceService.java
deleted file mode 100644 (file)
index a4b1fff..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-package org.argeo.app.core;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.ItemExistsException;
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.app.api.EntityType;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.maintenance.AbstractMaintenanceService;
-
-/** Base for custom initialisations. */
-public abstract class CustomMaintenanceService extends AbstractMaintenanceService {
-       private final static CmsLog log = CmsLog.getLog(AbstractMaintenanceService.class);
-
-       protected List<String> getTypologies() {
-               return new ArrayList<>();
-       }
-
-       protected String getTypologiesLoadBase() {
-               return "";
-       }
-
-       protected void loadTypologies(Node customBaseNode) throws RepositoryException, IOException {
-               List<String> typologies = getTypologies();
-               if (!typologies.isEmpty()) {
-                       Node termsBase = JcrUtils.getOrAdd(customBaseNode, EntityType.terms.name(), EntityType.typologies.get());
-                       for (String terms : typologies) {
-                               loadTerms(termsBase, terms);
-                       }
-                       // TODO do not save here, so that upper layers can decide when to save
-                       termsBase.getSession().save();
-               }
-       }
-
-       protected void loadTerms(Node termsBase, String name) throws IOException, RepositoryException {
-               try {
-//                     if (termsBase.hasNode(name))
-//                             return;
-                       String typologiesLoadBase = getTypologiesLoadBase();
-                       if (typologiesLoadBase.contains("/") && !typologiesLoadBase.endsWith("/"))
-                               typologiesLoadBase = typologiesLoadBase + "/";
-                       String termsLoadPath = typologiesLoadBase + name + ".xml";
-                       URL termsUrl = getClass().getResource(termsLoadPath);
-                       if (termsUrl == null)
-                               throw new IllegalArgumentException("Terms '" + name + "' not found.");
-                       try (InputStream in = termsUrl.openStream()) {
-                               termsBase.getSession().importXML(termsBase.getPath(), in,
-                                               ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-                       } catch (ItemExistsException e) {
-                               log.warn("Terms " + name + " exists with another UUID, removing it...");
-                               termsBase.getNode(name).remove();
-                               try (InputStream in = termsUrl.openStream()) {
-                                       termsBase.getSession().importXML(termsBase.getPath(), in,
-                                                       ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-                               }
-                       }
-                       if (log.isDebugEnabled())
-                               log.debug("Terms '" + name + "' loaded.");
-                       // TODO do not save here, so that upper layers can decide when to save
-                       termsBase.getSession().save();
-               } catch (RepositoryException | IOException e) {
-                       log.error("Cannot load terms '" + name + "': " + e.getMessage());
-                       throw e;
-               }
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/core/JcrEntityDefinition.java b/org.argeo.app.core/src/org/argeo/app/core/JcrEntityDefinition.java
deleted file mode 100644 (file)
index 10c27a8..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.argeo.app.core;
-
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.app.api.EntityConstants;
-import org.argeo.app.api.EntityDefinition;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jcr.Jcr;
-import org.osgi.framework.BundleContext;
-
-/** An entity definition based on a JCR data structure. */
-public class JcrEntityDefinition implements EntityDefinition {
-       private Repository repository;
-
-       private String type;
-       private String defaultEditorId;
-
-       public void init(BundleContext bundleContext, Map<String, String> properties) throws RepositoryException {
-               Session adminSession = CmsJcrUtils.openDataAdminSession(repository, null);
-               try {
-                       type = properties.get(EntityConstants.TYPE);
-                       if (type == null)
-                               throw new IllegalArgumentException("Entity type property " + EntityConstants.TYPE + " must be set.");
-                       defaultEditorId = properties.get(EntityConstants.DEFAULT_EDITOR_ID);
-//                     String definitionPath = EntityNames.ENTITY_DEFINITIONS_PATH + '/' + type;
-//                     if (!adminSession.itemExists(definitionPath)) {
-//                             Node entityDefinition = JcrUtils.mkdirs(adminSession, definitionPath, EntityTypes.ENTITY_DEFINITION);
-////                           entityDefinition.addMixin(EntityTypes.ENTITY_DEFINITION);
-//                             adminSession.save();
-//                     }
-                       initJcr(adminSession);
-               } finally {
-                       Jcr.logout(adminSession);
-               }
-       }
-
-       /** To be overridden in order to perform additional initialisations. */
-       protected void initJcr(Session adminSession) throws RepositoryException {
-
-       }
-
-       public void destroy(BundleContext bundleContext, Map<String, String> properties) throws RepositoryException {
-
-       }
-
-       @Override
-       public String getEditorId(Node entity) {
-               return defaultEditorId;
-       }
-
-       @Override
-       public String getType() {
-               return type;
-       }
-
-       protected Repository getRepository() {
-               return repository;
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       public String toString() {
-               return "Entity Definition " + getType();
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenance.java b/org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenance.java
new file mode 100644 (file)
index 0000000..2104145
--- /dev/null
@@ -0,0 +1,70 @@
+package org.argeo.app.core;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.measure.Quantity;
+import javax.measure.quantity.Area;
+
+import org.argeo.api.acr.spi.ContentNamespace;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.geotools.gml3.v3_2.GML;
+
+import si.uom.SI;
+import tech.units.indriya.quantity.Quantities;
+
+/**
+ * Background service starting and stopping with the whole system, and making
+ * sure it is in a proper state.
+ */
+public class SuiteMaintenance {
+       private ProvidedRepository contentRepository;
+
+       public void start() {
+               // make sure that the unit system is initialised
+               Quantity<Area> dummy = Quantities.getQuantity(0, SI.SQUARE_METRE);
+
+               getContentRepository().registerTypes(SuiteContentNamespace.values());
+//             for (SuiteContentTypes types : SuiteContentTypes.values()) {
+//                     getContentRepository().registerTypes(types.getDefaultPrefix(), types.getNamespace(),
+//                                     types.getResource() != null ? types.getResource().toExternalForm() : null);
+//             }
+
+               // GML schema import fails because of xlinks issues
+               getContentRepository().registerTypes(new ContentNamespace() {
+
+                       @Override
+                       public URL getSchemaResource() {
+                               try {
+                                       return new URL(GML.getInstance().getSchemaLocation());
+                               } catch (MalformedURLException e) {
+                                       throw new IllegalArgumentException(e);
+                               }
+                       }
+
+                       @Override
+                       public String getNamespaceURI() {
+                               return GML.getInstance().getNamespaceURI();
+                       }
+
+                       @Override
+                       public String getDefaultPrefix() {
+                               return "gml";
+                       }
+               });
+
+       }
+
+       public void stop() {
+
+       }
+
+       protected ProvidedRepository getContentRepository() {
+               return contentRepository;
+       }
+
+       public void setContentRepository(ProvidedRepository contentRepository) {
+               this.contentRepository = contentRepository;
+       }
+
+}
diff --git a/org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenanceService.java b/org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenanceService.java
deleted file mode 100644 (file)
index 9c74dde..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-package org.argeo.app.core;
-
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.security.Privilege;
-import javax.measure.Quantity;
-import javax.measure.quantity.Area;
-
-import org.argeo.api.acr.spi.ContentNamespace;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.app.api.EntityType;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.maintenance.AbstractMaintenanceService;
-import org.geotools.gml3.v3_2.GML;
-
-import si.uom.SI;
-import tech.units.indriya.quantity.Quantities;
-
-/** Initialises an Argeo Suite backend. */
-public class SuiteMaintenanceService extends AbstractMaintenanceService {
-       @Override
-       public void init() {
-               // make sure that the unit system is initialised
-               Quantity<Area> dummy = Quantities.getQuantity(0, SI.SQUARE_METRE);
-
-               super.init();
-
-               getContentRepository().registerTypes(SuiteContentNamespace.values());
-//             for (SuiteContentTypes types : SuiteContentTypes.values()) {
-//                     getContentRepository().registerTypes(types.getDefaultPrefix(), types.getNamespace(),
-//                                     types.getResource() != null ? types.getResource().toExternalForm() : null);
-//             }
-
-               // GML schema import fails because of xlinks issues
-               getContentRepository().registerTypes(new ContentNamespace() {
-
-                       @Override
-                       public URL getSchemaResource() {
-                               try {
-                                       return new URL(GML.getInstance().getSchemaLocation());
-                               } catch (MalformedURLException e) {
-                                       throw new IllegalArgumentException(e);
-                               }
-                       }
-
-                       @Override
-                       public String getNamespaceURI() {
-                               return GML.getInstance().getNamespaceURI();
-                       }
-
-                       @Override
-                       public String getDefaultPrefix() {
-                               return "gml";
-                       }
-               });
-       }
-
-       @Override
-       public boolean prepareJcrTree(Session adminSession) throws RepositoryException, IOException {
-               boolean modified = false;
-               Node rootNode = adminSession.getRootNode();
-               if (!rootNode.hasNode(EntityType.user.name())) {
-                       rootNode.addNode(EntityType.user.name(), NodeType.NT_UNSTRUCTURED);
-                       modified = true;
-               }
-               if (modified)
-                       adminSession.save();
-               return modified;
-       }
-
-       @Override
-       public void configurePrivileges(Session adminSession) throws RepositoryException {
-               JcrUtils.addPrivilege(adminSession, EntityType.user.basePath(), CmsConstants.ROLE_USER_ADMIN,
-                               Privilege.JCR_ALL);
-               // JcrUtils.addPrivilege(adminSession, "/", SuiteRole.coworker.dn(),
-               // Privilege.JCR_READ);
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/core/SuiteTerm.java b/org.argeo.app.core/src/org/argeo/app/core/SuiteTerm.java
deleted file mode 100644 (file)
index aef23b5..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.argeo.app.core;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.argeo.app.api.Term;
-
-/**
- * A single term. Helper to optimise {@link SuiteTermsManager} implementation.
- */
-class SuiteTerm implements Term {
-       private final String name;
-       private final String relativePath;
-       private final SuiteTypology typology;
-       private final String id;
-
-       private final SuiteTerm parentTerm;
-       private final List<SuiteTerm> subTerms = new ArrayList<>();
-
-       SuiteTerm(SuiteTypology typology, String relativePath, SuiteTerm parentTerm) {
-               this.typology = typology;
-               this.parentTerm = parentTerm;
-               this.relativePath = relativePath;
-               int index = relativePath.lastIndexOf('/');
-               if (index > 0) {
-                       this.name = relativePath.substring(index + 1);
-               } else {
-                       this.name = relativePath;
-               }
-               id = typology.getName() + '/' + relativePath;
-       }
-
-       @Override
-       public String getId() {
-               return id;
-       }
-
-       @Override
-       public String getName() {
-               return name;
-       }
-
-       public String getRelativePath() {
-               return relativePath;
-       }
-
-       @Override
-       public SuiteTypology getTypology() {
-               return typology;
-       }
-
-       @Override
-       public List<SuiteTerm> getSubTerms() {
-               return subTerms;
-       }
-
-       @Override
-       public SuiteTerm getParentTerm() {
-               return parentTerm;
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/core/SuiteTermsManager.java b/org.argeo.app.core/src/org/argeo/app/core/SuiteTermsManager.java
deleted file mode 100644 (file)
index c14f871..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-package org.argeo.app.core;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.app.api.EntityNames;
-import org.argeo.app.api.EntityType;
-import org.argeo.app.api.Term;
-import org.argeo.app.api.TermsManager;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-
-/** Argeo Suite implementation of terms manager. */
-public class SuiteTermsManager implements TermsManager {
-       private final Map<String, SuiteTerm> terms = new HashMap<>();
-       private final Map<String, SuiteTypology> typologies = new HashMap<>();
-
-       // JCR
-       private Repository repository;
-       private Session adminSession;
-
-       public void init() {
-               adminSession = CmsJcrUtils.openDataAdminSession(repository, CmsConstants.SYS_WORKSPACE);
-       }
-
-       @Override
-       public List<Term> listAllTerms(String typology) {
-               List<Term> res = new ArrayList<>();
-               SuiteTypology t = getTypology(typology);
-               for (SuiteTerm term : t.getAllTerms()) {
-                       res.add(term);
-               }
-               return res;
-       }
-
-       @Override
-       public SuiteTerm getTerm(String termId) {
-               return terms.get(termId);
-       }
-
-       @Override
-       public SuiteTypology getTypology(String typology) {
-               SuiteTypology t = typologies.get(typology);
-               if (t == null) {
-                       Node termsNode = Jcr.getNode(adminSession, "SELECT * FROM [{0}] WHERE NAME()=\"{1}\"",
-                                       EntityType.terms.get(), typology);
-                       if (termsNode == null)
-                               throw new IllegalArgumentException("Typology " + typology + " not found.");
-                       t = loadTypology(termsNode);
-               }
-               return t;
-       }
-
-       SuiteTypology loadTypology(Node termsNode) {
-               try {
-                       SuiteTypology typology = new SuiteTypology(termsNode);
-                       for (Node termNode : Jcr.iterate(termsNode.getNodes())) {
-                               if (termNode.isNodeType(EntityType.term.get())) {
-                                       SuiteTerm term = loadTerm(typology, termNode, null);
-                                       if (!term.getSubTerms().isEmpty())
-                                               typology.markNotFlat();
-                                       typology.getSubTerms().add(term);
-                               }
-                       }
-                       typologies.put(typology.getName(), typology);
-                       return typology;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot load typology from " + termsNode, e);
-               }
-       }
-
-       SuiteTerm loadTerm(SuiteTypology typology, Node termNode, SuiteTerm parentTerm) throws RepositoryException {
-               String name = termNode.getProperty(EntityNames.NAME).getString();
-               String relativePath = parentTerm == null ? name : parentTerm.getRelativePath() + '/' + name;
-               SuiteTerm term = new SuiteTerm(typology, relativePath, parentTerm);
-               terms.put(term.getId(), term);
-               for (Node subTermNode : Jcr.iterate(termNode.getNodes())) {
-                       if (termNode.isNodeType(EntityType.term.get())) {
-                               SuiteTerm subTerm = loadTerm(typology, subTermNode, term);
-                               term.getSubTerms().add(subTerm);
-                       }
-               }
-               return term;
-       }
-
-       public void destroy() {
-               Jcr.logout(adminSession);
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/core/SuiteTypology.java b/org.argeo.app.core/src/org/argeo/app/core/SuiteTypology.java
deleted file mode 100644 (file)
index 7838274..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-package org.argeo.app.core;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Node;
-
-import org.argeo.app.api.Term;
-import org.argeo.app.api.Typology;
-import org.argeo.jcr.Jcr;
-
-/** A typology. Helper to optimise {@link SuiteTermsManager} implementation. */
-class SuiteTypology implements Typology {
-       private final String name;
-       private final Node node;
-       private boolean isFlat = true;
-
-       private final List<SuiteTerm> subTerms = new ArrayList<>();
-
-       public SuiteTypology(Node node) {
-               this.node = node;
-               this.name = Jcr.getName(this.node);
-       }
-
-       @Override
-       public String getId() {
-               return name;
-       }
-
-       public String getName() {
-               return name;
-       }
-
-       public Node getNode() {
-               return node;
-       }
-
-       void markNotFlat() {
-               if (isFlat)
-                       isFlat = false;
-       }
-
-       @Override
-       public boolean isFlat() {
-               return isFlat;
-       }
-
-       @Override
-       public List<SuiteTerm> getSubTerms() {
-               return subTerms;
-       }
-
-       public List<SuiteTerm> getAllTerms() {
-               if (isFlat)
-                       return subTerms;
-               else {
-                       List<SuiteTerm> terms = new ArrayList<>();
-                       for (SuiteTerm subTerm : subTerms) {
-                               terms.add(subTerm);
-                               collectSubTerms(terms, subTerm);
-                       }
-                       return terms;
-               }
-       }
-
-       public Term findTermByName(String name) {
-               List<SuiteTerm> collected = new ArrayList<>();
-               for (SuiteTerm subTerm : subTerms) {
-                       collectTermsByName(subTerm, name, collected);
-               }
-               if (collected.isEmpty())
-                       return null;
-               if (collected.size() == 1)
-                       return collected.get(0);
-               throw new IllegalArgumentException(
-                               "There are " + collected.size() + " terms with name " + name + " in typology " + getId());
-       }
-
-       private void collectTermsByName(SuiteTerm term, String name, List<SuiteTerm> collected) {
-               if (term.getName().equals(name)) {
-                       collected.add(term);
-               }
-               for (SuiteTerm subTerm : term.getSubTerms()) {
-                       collectTermsByName(subTerm, name, collected);
-               }
-       }
-
-       private void collectSubTerms(List<SuiteTerm> terms, SuiteTerm term) {
-               for (SuiteTerm subTerm : term.getSubTerms()) {
-                       terms.add(subTerm);
-                       collectSubTerms(terms, subTerm);
-               }
-       }
-
-}
index f225064158ac6728486de7805764070e04936c2b..7b614a74ea0939a5d494e0b3b7f90227442db2e6 100644 (file)
@@ -3,24 +3,13 @@ package org.argeo.app.core;
 import java.util.HashSet;
 import java.util.Set;
 
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.security.Privilege;
-import javax.security.auth.x500.X500Principal;
 import javax.xml.namespace.QName;
 
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ldap.LdapAttr;
 import org.argeo.api.acr.ldap.LdapObj;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsSession;
 import org.argeo.app.api.EntityType;
 import org.argeo.cms.RoleNameUtils;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
 
 /** Utilities around the Argeo Suite APIs. */
 public class SuiteUtils {
@@ -33,84 +22,6 @@ public class SuiteUtils {
                return EntityType.user.basePath() + '/' + uid;
        }
 
-       public static Node getOrCreateUserNode(Session adminSession, String userDn) {
-               try {
-                       Node usersBase = adminSession.getNode(EntityType.user.basePath());
-                       String uid = RoleNameUtils.getLastRdnValue(userDn);
-                       Node userNode;
-                       if (!usersBase.hasNode(uid)) {
-                               userNode = usersBase.addNode(uid, NodeType.NT_UNSTRUCTURED);
-                               userNode.addMixin(EntityType.user.get());
-                               userNode.addMixin(NodeType.MIX_CREATED);
-                               userNode.setProperty(LdapAttr.distinguishedName.get(), userDn.toString());
-                               userNode.setProperty(LdapAttr.uid.get(), uid);
-                       } else {
-                               userNode = usersBase.getNode(uid);
-                       }
-
-                       if (!userNode.hasNode(USER_SESSIONS_NODE_NAME)) {
-                               // Migrate existing user node
-                               Node sessionsNode = userNode.addNode(USER_SESSIONS_NODE_NAME, NodeType.NT_UNSTRUCTURED);
-                               oldSessions: for (NodeIterator nit = userNode.getNodes(); nit.hasNext();) {
-                                       Node child = nit.nextNode();
-                                       if (USER_SESSIONS_NODE_NAME.equals(child.getName()) || child.getName().startsWith("rep:")
-                                                       || child.getName().startsWith("jcr:"))
-                                               continue oldSessions;
-                                       Node target = sessionsNode.addNode(child.getName());
-                                       JcrUtils.copy(child, target);
-                               }
-
-                               Node userStateNode = userNode.addNode(USER_STATE_NODE_NAME, NodeType.NT_UNSTRUCTURED);
-                               Node userDevicesNode = userNode.addNode(USER_DEVICES_NODE_NAME, NodeType.NT_UNSTRUCTURED);
-
-                               adminSession.save();
-//                             JackrabbitSecurityUtils.denyPrivilege(adminSession, userNode.getPath(), SuiteRole.coworker.dn(),
-//                                             Privilege.JCR_READ);
-                               JcrUtils.addPrivilege(adminSession, userNode.getPath(), new X500Principal(userDn.toString()).getName(),
-                                               Privilege.JCR_READ);
-                               JcrUtils.addPrivilege(adminSession, userNode.getPath(), CmsConstants.ROLE_USER_ADMIN,
-                                               Privilege.JCR_ALL);
-
-                               JcrUtils.addPrivilege(adminSession, userStateNode.getPath(), userDn, Privilege.JCR_ALL);
-                               JcrUtils.addPrivilege(adminSession, userDevicesNode.getPath(), userDn, Privilege.JCR_ALL);
-                       }
-                       return userNode;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot create user node for " + userDn, e);
-               }
-       }
-
-       public static Node getCmsSessionNode(Session session, CmsSession cmsSession) {
-               try {
-                       return session.getNode(getUserNodePath(cmsSession.getUserDn()) + '/' + USER_SESSIONS_NODE_NAME + '/'
-                                       + cmsSession.getUuid().toString());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get session dir for " + cmsSession, e);
-               }
-       }
-
-       public static Node getOrCreateCmsSessionNode(Session adminSession, CmsSession cmsSession) {
-               try {
-                       String userDn = cmsSession.getUserDn();
-                       Node userNode = getOrCreateUserNode(adminSession, userDn);
-                       Node sessionsNode = userNode.getNode(USER_SESSIONS_NODE_NAME);
-                       String cmsSessionUuid = cmsSession.getUuid().toString();
-                       Node cmsSessionNode;
-                       if (!sessionsNode.hasNode(cmsSessionUuid)) {
-                               cmsSessionNode = sessionsNode.addNode(cmsSessionUuid, NodeType.NT_UNSTRUCTURED);
-                               cmsSessionNode.addMixin(NodeType.MIX_CREATED);
-                               adminSession.save();
-                               JcrUtils.addPrivilege(adminSession, cmsSessionNode.getPath(), cmsSession.getUserRole(),
-                                               Privilege.JCR_ALL);
-                       } else {
-                               cmsSessionNode = sessionsNode.getNode(cmsSessionUuid);
-                       }
-                       return cmsSessionNode;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot create session dir for " + cmsSession, e);
-               }
-       }
-
        public static Set<String> extractRoles(String[] semiColArr) {
                Set<String> res = new HashSet<>();
                // TODO factorize and make it more robust
diff --git a/org.argeo.app.core/src/org/argeo/app/core/XPathUtils.java b/org.argeo.app.core/src/org/argeo/app/core/XPathUtils.java
deleted file mode 100644 (file)
index b0678cd..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-package org.argeo.app.core;
-
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.query.Query;
-import javax.jcr.query.QueryManager;
-
-import org.apache.jackrabbit.util.ISO9075;
-import org.argeo.api.cms.CmsLog;
-
-/** Ease XPath generation for JCR requests */
-public class XPathUtils {
-       private final static CmsLog log = CmsLog.getLog(XPathUtils.class);
-
-       private final static String QUERY_XPATH = "xpath";
-
-       public static String descendantFrom(String parentPath) {
-               if (notEmpty(parentPath)) {
-                       if ("/".equals(parentPath))
-                               parentPath = "";
-                       // Hardcoded dependency to Jackrabbit. Remove
-                       String result = "/jcr:root" + ISO9075.encodePath(parentPath);
-                       if (log.isTraceEnabled()) {
-                               String result2 = "/jcr:root" + parentPath;
-                               if (!result2.equals(result))
-                                       log.warn("Encoded Path " + result2 + " --> " + result);
-                       }
-                       return result;
-               } else
-                       return "";
-       }
-
-       public static String localAnd(String... conditions) {
-               StringBuilder builder = new StringBuilder();
-               for (String condition : conditions) {
-                       if (notEmpty(condition)) {
-                               builder.append(" ").append(condition).append(" and ");
-                       }
-               }
-               if (builder.length() > 3)
-                       return builder.substring(0, builder.length() - 4);
-               else
-                       return "";
-       }
-
-       public static String xPathNot(String condition) {
-               if (notEmpty(condition))
-                       return "not(" + condition + ")";
-               else
-                       return "";
-       }
-
-       public static String getFreeTextConstraint(String filter) throws RepositoryException {
-               StringBuilder builder = new StringBuilder();
-               if (notEmpty(filter)) {
-                       String[] strs = filter.trim().split(" ");
-                       for (String token : strs) {
-                               builder.append("jcr:contains(.,'*" + encodeXPathStringValue(token) + "*') and ");
-                       }
-                       return builder.substring(0, builder.length() - 4);
-               }
-               return "";
-       }
-
-       public static String getPropertyContains(String propertyName, String filter) throws RepositoryException {
-               if (notEmpty(filter))
-                       return "jcr:contains(@" + propertyName + ",'*" + encodeXPathStringValue(filter) + "*')";
-               return "";
-       }
-
-       private final static DateFormat jcrRefFormatter = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSS'+02:00'");
-
-       /**
-        * @param propertyName
-        * @param calendar       the reference date
-        * @param lowerOrGreater "&lt;", "&gt;" TODO validate "&gt;="
-        * @throws RepositoryException
-        */
-       public static String getPropertyDateComparaison(String propertyName, Calendar cal, String lowerOrGreater)
-                       throws RepositoryException {
-               if (cal != null) {
-                       String jcrDateStr = jcrRefFormatter.format(cal.getTime());
-
-                       // jcrDateStr = "2015-08-03T05:00:03:000Z";
-                       String result = "@" + propertyName + " " + lowerOrGreater + " xs:dateTime('" + jcrDateStr + "')";
-                       return result;
-               }
-               return "";
-       }
-
-       public static String getPropertyEquals(String propertyName, String value) {
-               if (notEmpty(value))
-                       return "@" + propertyName + "='" + encodeXPathStringValue(value) + "'";
-               return "";
-       }
-
-       public static String encodeXPathStringValue(String propertyValue) {
-               // TODO implement safer mechanism to escape invalid characters
-               // Also check why we have used this regex in ResourceSerrviceImpl l 474
-               // String cleanedKey = key.replaceAll("(?:')", "''");
-               String result = propertyValue.replaceAll("'", "''");
-               return result;
-       }
-
-       public static void andAppend(StringBuilder builder, String condition) {
-               if (notEmpty(condition)) {
-                       builder.append(condition);
-                       builder.append(" and ");
-               }
-       }
-
-       public static void appendOrderByProperties(StringBuilder builder, boolean ascending, String... propertyNames) {
-               if (propertyNames.length > 0) {
-                       builder.append(" order by ");
-                       for (String propName : propertyNames)
-                               builder.append("@").append(propName).append(", ");
-                       builder = builder.delete(builder.length() - 2, builder.length());
-                       if (ascending)
-                               builder.append(" ascending ");
-                       else
-                               builder.append(" descending ");
-               }
-       }
-
-       public static void appendAndPropStringCondition(StringBuilder builder, String propertyName, String filter)
-                       throws RepositoryException {
-               if (notEmpty(filter)) {
-                       andAppend(builder, getPropertyContains(propertyName, filter));
-               }
-       }
-
-       public static void appendAndNotPropStringCondition(StringBuilder builder, String propertyName, String filter)
-                       throws RepositoryException {
-               if (notEmpty(filter)) {
-                       String cond = getPropertyContains(propertyName, filter);
-                       builder.append(xPathNot(cond));
-                       builder.append(" and ");
-               }
-       }
-
-       public static Query createQuery(Session session, String queryString) throws RepositoryException {
-               QueryManager queryManager = session.getWorkspace().getQueryManager();
-               // Localise JCR properties for XPATH
-               queryString = localiseJcrItemNames(queryString);
-               return queryManager.createQuery(queryString, QUERY_XPATH);
-       }
-
-       private final static String NS_JCR = "\\{http://www.jcp.org/jcr/1.0\\}";
-       private final static String NS_NT = "\\{http://www.jcp.org/jcr/nt/1.0\\}";
-       private final static String NS_MIX = "\\{http://www.jcp.org/jcr/mix/1.0\\}";
-
-       /**
-        * Replace the generic namespace with the local "jcr:", "nt:", "mix:" values. It
-        * is a workaround that must be later cleaned
-        */
-       public static String localiseJcrItemNames(String name) {
-               name = name.replaceAll(NS_JCR, "jcr:");
-               name = name.replaceAll(NS_NT, "nt:");
-               name = name.replaceAll(NS_MIX, "mix:");
-               return name;
-       }
-
-       private static boolean notEmpty(String stringToTest) {
-               return !(stringToTest == null || "".equals(stringToTest.trim()));
-       }
-
-       /** Singleton. */
-       private XPathUtils() {
-
-       }
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/docbook/Dbk4Converter.java b/org.argeo.app.core/src/org/argeo/app/docbook/Dbk4Converter.java
deleted file mode 100644 (file)
index f213c02..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-package org.argeo.app.docbook;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.Result;
-import javax.xml.transform.Source;
-import javax.xml.transform.Templates;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerConfigurationException;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-import javax.xml.transform.stream.StreamSource;
-
-import org.argeo.jcr.JcrException;
-import org.w3c.dom.Document;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-
-import net.sf.saxon.BasicTransformerFactory;
-import net.sf.saxon.TransformerFactoryImpl;
-
-/** Convert from DocBook v4 to DocBook v5, using the official XSL. */
-public class Dbk4Converter {
-       private final Templates templates;
-
-       public Dbk4Converter() {
-               try (InputStream in = getClass().getResourceAsStream("db4-upgrade.xsl")) {
-                       Source xsl = new StreamSource(in);
-                       TransformerFactory transformerFactory = new BasicTransformerFactory();
-//                     TransformerFactory transformerFactory = new TransformerFactoryImpl();
-                       templates = transformerFactory.newTemplates(xsl);
-               } catch (IOException | TransformerConfigurationException e) {
-                       throw new RuntimeException("Cannot initialise DocBook v4 converter", e);
-               }
-       }
-
-       public void importXml(Node baseNode, InputStream in) throws IOException {
-               try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
-                       DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-                       factory.setXIncludeAware(true);
-                       factory.setNamespaceAware(true);
-                       DocumentBuilder docBuilder = factory.newDocumentBuilder();
-                       Document doc = docBuilder.parse(new InputSource(in));
-                       Source xmlInput = new DOMSource(doc);
-
-//                     ContentHandler contentHandler = baseNode.getSession().getImportContentHandler(baseNode.getPath(),
-//                                     ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-
-                       Transformer transformer = templates.newTransformer();
-                       Result xmlOutput = new StreamResult(out);
-                       transformer.transform(xmlInput, xmlOutput);
-                       try (InputStream dbk5in = new ByteArrayInputStream(out.toByteArray())) {
-                               baseNode.getSession().importXML(baseNode.getPath(), dbk5in,
-                                               ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot import XML to " + baseNode, e);
-               } catch (TransformerException | SAXException | ParserConfigurationException e) {
-                       throw new RuntimeException("Cannot import DocBook v4 to " + baseNode, e);
-               }
-
-       }
-
-       public static void main(String[] args) {
-               try {
-
-                       Source xsl = new StreamSource(new File("/usr/share/xml/docbook5/stylesheet/upgrade/db4-upgrade.xsl"));
-                       TransformerFactory transformerFactory = new TransformerFactoryImpl();
-                       Templates templates = transformerFactory.newTemplates(xsl);
-
-                       File inputDir = new File(args[0]);
-                       File outputDir = new File(args[1]);
-
-                       for (File inputFile : inputDir.listFiles()) {
-                               Result xmlOutput = new StreamResult(new File(outputDir, inputFile.getName()));
-
-                               DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-                               factory.setXIncludeAware(true);
-                               factory.setNamespaceAware(true);
-                               DocumentBuilder docBuilder = factory.newDocumentBuilder();
-                               Document doc = docBuilder.parse(inputFile);
-                               Source xmlInput = new DOMSource(doc);
-                               Transformer transformer = templates.newTransformer();
-                               transformer.transform(xmlInput, xmlOutput);
-                       }
-               } catch (Throwable e) {
-                       e.printStackTrace();
-               }
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/docbook/DbkUtils.java b/org.argeo.app.core/src/org/argeo/app/docbook/DbkUtils.java
deleted file mode 100644 (file)
index b0d352b..0000000
+++ /dev/null
@@ -1,253 +0,0 @@
-package org.argeo.app.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.PathNotFoundException;
-import javax.jcr.RepositoryException;
-import javax.jcr.ValueFormatException;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.app.api.EntityType;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.JcrxApi;
-
-/** Utilities around DocBook. */
-public class DbkUtils {
-       private final static CmsLog log = CmsLog.getLog(DbkUtils.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 DbkUtils() {
-       }
-
-}
diff --git a/org.argeo.app.core/src/org/argeo/app/docbook/db4-upgrade.xsl b/org.argeo.app.core/src/org/argeo/app/docbook/db4-upgrade.xsl
deleted file mode 100644 (file)
index 00096be..0000000
+++ /dev/null
@@ -1,1398 +0,0 @@
-<?xml version="1.0"?>
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
-                xmlns:exsl="http://exslt.org/common"
-               xmlns:db = "http://docbook.org/ns/docbook"
-               xmlns:xlink="http://www.w3.org/1999/xlink"
-                exclude-result-prefixes="exsl db"
-                version="1.0">
-
-<!--
-# ======================================================================
-# This file is part of DocBook V5.0CR5
-#
-# Copyright 2005 Norman Walsh, Sun Microsystems, Inc., and the
-# Organization for the Advancement of Structured Information
-# Standards (OASIS).
-#
-# Release: $Id: db4-upgrade.xsl 7660 2008-02-06 13:48:36Z nwalsh $
-#
-# Permission to use, copy, modify and distribute this stylesheet
-# and its accompanying documentation for any purpose and without fee
-# is hereby granted in perpetuity, provided that the above copyright
-# notice and this paragraph appear in all copies. The copyright
-# holders make no representation about the suitability of the schema
-# for any purpose. It is provided "as is" without expressed or implied
-# warranty.
-#
-# Please direct all questions, bug reports, or suggestions for changes
-# to the docbook@lists.oasis-open.org mailing list. For more
-# information, see http://www.oasis-open.org/docbook/.
-#
-# ======================================================================
--->
-
-<xsl:variable name="version" select="'1.0'"/>
-
-<xsl:output method="xml" encoding="utf-8" indent="no" omit-xml-declaration="yes"/>
-
-<xsl:preserve-space elements="*"/>
-<xsl:param name="rootid">
-  <xsl:choose>
-  <xsl:when test="/*/@id">
-    <xsl:value-of select="/*/@id"/>
-  </xsl:when>
-  <xsl:otherwise>
-    <xsl:text>UNKNOWN</xsl:text>
-  </xsl:otherwise>
-  </xsl:choose>
-</xsl:param>
-
-<xsl:param name="defaultDate" select="''"/>
-
-<xsl:template match="/">
-  <xsl:variable name="converted">
-    <xsl:apply-templates/>
-  </xsl:variable>
-  <xsl:comment>
-    <xsl:text> Converted by db4-upgrade version </xsl:text>
-    <xsl:value-of select="$version"/>
-    <xsl:text> </xsl:text>
-  </xsl:comment>
-  <xsl:text>&#10;</xsl:text>
-  <xsl:apply-templates select="exsl:node-set($converted)/*" mode="addNS"/>
-</xsl:template>
-
-<xsl:template match="bookinfo|chapterinfo|articleinfo|artheader|appendixinfo
-                    |blockinfo
-                     |bibliographyinfo|glossaryinfo|indexinfo|setinfo
-                    |setindexinfo
-                     |sect1info|sect2info|sect3info|sect4info|sect5info
-                     |sectioninfo
-                     |refsect1info|refsect2info|refsect3info|refsectioninfo
-                    |referenceinfo|partinfo"
-              priority="200">
-  <info>
-    <xsl:call-template name="copy.attributes"/>
-
-    <!-- titles can be inside or outside or both. fix that -->
-    <xsl:choose>
-      <xsl:when test="title and following-sibling::title">
-        <xsl:if test="title != following-sibling::title">
-          <xsl:call-template name="emit-message">
-            <xsl:with-param name="message">
-              <xsl:text>Check </xsl:text>
-              <xsl:value-of select="name(..)"/>
-              <xsl:text> title.</xsl:text>
-            </xsl:with-param>
-          </xsl:call-template>
-        </xsl:if>
-        <xsl:apply-templates select="title" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="title">
-        <xsl:apply-templates select="title" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="following-sibling::title">
-        <xsl:apply-templates select="following-sibling::title" mode="copy"/>
-      </xsl:when>
-      <xsl:otherwise>
-        <xsl:call-template name="emit-message">
-          <xsl:with-param name="message">
-            <xsl:text>Check </xsl:text>
-            <xsl:value-of select="name(..)"/>
-            <xsl:text>: no title.</xsl:text>
-          </xsl:with-param>
-        </xsl:call-template>
-      </xsl:otherwise>
-    </xsl:choose>
-
-    <xsl:choose>
-      <xsl:when test="titleabbrev and following-sibling::titleabbrev">
-        <xsl:if test="titleabbrev != following-sibling::titleabbrev">
-          <xsl:call-template name="emit-message">
-            <xsl:with-param name="message">
-              <xsl:text>Check </xsl:text>
-              <xsl:value-of select="name(..)"/>
-              <xsl:text> titleabbrev.</xsl:text>
-            </xsl:with-param>
-          </xsl:call-template>
-        </xsl:if>
-        <xsl:apply-templates select="titleabbrev" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="titleabbrev">
-        <xsl:apply-templates select="titleabbrev" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="following-sibling::titleabbrev">
-        <xsl:apply-templates select="following-sibling::titleabbrev" mode="copy"/>
-      </xsl:when>
-    </xsl:choose>
-
-    <xsl:choose>
-      <xsl:when test="subtitle and following-sibling::subtitle">
-        <xsl:if test="subtitle != following-sibling::subtitle">
-          <xsl:call-template name="emit-message">
-            <xsl:with-param name="message">
-              <xsl:text>Check </xsl:text>
-              <xsl:value-of select="name(..)"/>
-              <xsl:text> subtitle.</xsl:text>
-            </xsl:with-param>
-          </xsl:call-template>
-        </xsl:if>
-        <xsl:apply-templates select="subtitle" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="subtitle">
-        <xsl:apply-templates select="subtitle" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="following-sibling::subtitle">
-        <xsl:apply-templates select="following-sibling::subtitle" mode="copy"/>
-      </xsl:when>
-    </xsl:choose>
-
-    <xsl:apply-templates/>
-  </info>
-</xsl:template>
-
-<xsl:template match="objectinfo|prefaceinfo|refsynopsisdivinfo
-                    |screeninfo|sidebarinfo"
-             priority="200">
-  <info>
-    <xsl:call-template name="copy.attributes"/>
-
-    <!-- titles can be inside or outside or both. fix that -->
-    <xsl:choose>
-      <xsl:when test="title and following-sibling::title">
-        <xsl:if test="title != following-sibling::title">
-          <xsl:call-template name="emit-message">
-            <xsl:with-param name="message">
-              <xsl:text>Check </xsl:text>
-              <xsl:value-of select="name(..)"/>
-              <xsl:text> title.</xsl:text>
-            </xsl:with-param>
-          </xsl:call-template>
-        </xsl:if>
-        <xsl:apply-templates select="title" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="title">
-        <xsl:apply-templates select="title" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="following-sibling::title">
-        <xsl:apply-templates select="following-sibling::title" mode="copy"/>
-      </xsl:when>
-      <xsl:otherwise>
-       <!-- it's ok if there's no title on these -->
-      </xsl:otherwise>
-    </xsl:choose>
-
-    <xsl:choose>
-      <xsl:when test="titleabbrev and following-sibling::titleabbrev">
-        <xsl:if test="titleabbrev != following-sibling::titleabbrev">
-          <xsl:call-template name="emit-message">
-          <xsl:with-param name="message">
-            <xsl:text>Check </xsl:text>
-            <xsl:value-of select="name(..)"/>
-            <xsl:text> titleabbrev.</xsl:text>
-          </xsl:with-param>
-        </xsl:call-template>
-        </xsl:if>
-        <xsl:apply-templates select="titleabbrev" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="titleabbrev">
-        <xsl:apply-templates select="titleabbrev" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="following-sibling::titleabbrev">
-        <xsl:apply-templates select="following-sibling::titleabbrev" mode="copy"/>
-      </xsl:when>
-    </xsl:choose>
-
-    <xsl:choose>
-      <xsl:when test="subtitle and following-sibling::subtitle">
-        <xsl:if test="subtitle != following-sibling::subtitle">
-          <xsl:call-template name="emit-message">
-            <xsl:with-param name="message">
-              <xsl:text>Check </xsl:text>
-              <xsl:value-of select="name(..)"/>
-              <xsl:text> subtitle.</xsl:text>
-            </xsl:with-param>
-          </xsl:call-template>
-        </xsl:if>
-        <xsl:apply-templates select="subtitle" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="subtitle">
-        <xsl:apply-templates select="subtitle" mode="copy"/>
-      </xsl:when>
-      <xsl:when test="following-sibling::subtitle">
-        <xsl:apply-templates select="following-sibling::subtitle" mode="copy"/>
-      </xsl:when>
-    </xsl:choose>
-
-    <xsl:apply-templates/>
-  </info>
-</xsl:template>
-
-<xsl:template match="refentryinfo"
-              priority="200">
-  <info>
-    <xsl:call-template name="copy.attributes"/>
-
-    <!-- titles can be inside or outside or both. fix that -->
-    <xsl:if test="title">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Discarding title from refentryinfo!</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-    </xsl:if>
-
-    <xsl:if test="titleabbrev">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Discarding titleabbrev from refentryinfo!</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-    </xsl:if>
-
-    <xsl:if test="subtitle">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Discarding subtitle from refentryinfo!</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-    </xsl:if>
-
-    <xsl:apply-templates/>
-  </info>
-</xsl:template>
-
-<xsl:template match="refmiscinfo"
-              priority="200">
-  <refmiscinfo>
-    <xsl:call-template name="copy.attributes">
-      <xsl:with-param name="suppress" select="'class'"/>
-    </xsl:call-template>
-    <xsl:if test="@class">
-      <xsl:choose>
-       <xsl:when test="@class = 'source'
-                       or @class = 'version'
-                       or @class = 'manual'
-                       or @class = 'sectdesc'
-                       or @class = 'software'">
-         <xsl:attribute name="class">
-           <xsl:value-of select="@class"/>
-         </xsl:attribute>
-       </xsl:when>
-       <xsl:otherwise>
-         <xsl:attribute name="class">
-           <xsl:value-of select="'other'"/>
-         </xsl:attribute>
-         <xsl:attribute name="otherclass">
-           <xsl:value-of select="@class"/>
-         </xsl:attribute>
-       </xsl:otherwise>
-      </xsl:choose>
-    </xsl:if>
-    <xsl:apply-templates/>
-  </refmiscinfo>
-</xsl:template>
-
-<xsl:template match="corpauthor" priority="200">
-  <author>
-    <xsl:call-template name="copy.attributes"/>
-    <orgname>
-      <xsl:apply-templates/>
-    </orgname>
-  </author>
-</xsl:template>
-
-<xsl:template match="corpname" priority="200">
-  <orgname>
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </orgname>
-</xsl:template>
-
-<xsl:template match="author[not(personname)]|editor[not(personname)]|othercredit[not(personname)]" priority="200">
-  <xsl:copy>
-    <xsl:call-template name="copy.attributes"/>
-    <personname>
-      <xsl:apply-templates select="honorific|firstname|surname|othername|lineage"/>
-    </personname>
-    <xsl:apply-templates select="*[not(self::honorific|self::firstname|self::surname
-                                   |self::othername|self::lineage)]"/>
-  </xsl:copy>
-</xsl:template>
-
-<xsl:template match="address|programlisting|screen|funcsynopsisinfo
-                     |classsynopsisinfo|literallayout" priority="200">
-  <xsl:copy>
-    <xsl:call-template name="copy.attributes">
-      <xsl:with-param name="suppress" select="'format'"/>
-    </xsl:call-template>
-    <xsl:apply-templates/>
-  </xsl:copy>
-</xsl:template>
-
-<xsl:template match="productname[@class]" priority="200">
-  <xsl:call-template name="emit-message">
-    <xsl:with-param name="message">
-      <xsl:text>Dropping class attribute from productname</xsl:text>
-    </xsl:with-param>
-  </xsl:call-template>
-  <xsl:copy>
-    <xsl:call-template name="copy.attributes">
-      <xsl:with-param name="suppress" select="'class'"/>
-    </xsl:call-template>
-    <xsl:apply-templates/>
-  </xsl:copy>
-</xsl:template>
-
-<xsl:template match="dedication|preface|chapter|appendix|part|partintro
-                     |article|bibliography|glossary|glossdiv|index
-                    |reference[not(referenceinfo)]
-                     |book" priority="200">
-  <xsl:choose>
-    <xsl:when test="not(dedicationinfo|prefaceinfo|chapterinfo
-                       |appendixinfo|partinfo
-                        |articleinfo|artheader|bibliographyinfo
-                       |glossaryinfo|indexinfo
-                        |bookinfo)">
-      <xsl:copy>
-        <xsl:call-template name="copy.attributes"/>
-        <xsl:if test="title|subtitle|titleabbrev">
-          <info>
-            <xsl:apply-templates select="title" mode="copy"/>
-            <xsl:apply-templates select="titleabbrev" mode="copy"/>
-            <xsl:apply-templates select="subtitle" mode="copy"/>
-            <xsl:apply-templates select="abstract" mode="copy"/>
-          </info>
-        </xsl:if>
-        <xsl:apply-templates/>
-      </xsl:copy>
-    </xsl:when>
-    <xsl:otherwise>
-      <xsl:copy>
-        <xsl:call-template name="copy.attributes"/>
-        <xsl:apply-templates/>
-      </xsl:copy>
-    </xsl:otherwise>
-  </xsl:choose>
-</xsl:template>
-
-<xsl:template match="formalpara|figure|table[tgroup]|example|blockquote
-                     |caution|important|note|warning|tip
-                     |bibliodiv|glossarydiv|indexdiv
-                    |orderedlist|itemizedlist|variablelist|procedure
-                    |task|tasksummary|taskprerequisites|taskrelated
-                    |sidebar"
-             priority="200">
-  <xsl:choose>
-    <xsl:when test="blockinfo">
-      <xsl:copy>
-        <xsl:call-template name="copy.attributes"/>
-        <xsl:apply-templates/>
-      </xsl:copy>
-    </xsl:when>
-    <xsl:otherwise>
-      <xsl:copy>
-        <xsl:call-template name="copy.attributes"/>
-
-       <xsl:if test="title|titleabbrev|subtitle">
-         <info>
-           <xsl:apply-templates select="title" mode="copy"/>
-           <xsl:apply-templates select="titleabbrev" mode="copy"/>
-           <xsl:apply-templates select="subtitle" mode="copy"/>
-         </info>
-       </xsl:if>
-
-        <xsl:apply-templates/>
-      </xsl:copy>
-    </xsl:otherwise>
-  </xsl:choose>
-</xsl:template>
-
-<xsl:template match="equation" priority="200">
-  <xsl:choose>
-    <xsl:when test="not(title)">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param
-            name="message"
-            >Convert equation without title to informal equation.</xsl:with-param>
-      </xsl:call-template>
-      <informalequation>
-        <xsl:call-template name="copy.attributes"/>
-        <xsl:apply-templates/>
-      </informalequation>
-    </xsl:when>
-    <xsl:when test="blockinfo">
-      <xsl:copy>
-        <xsl:call-template name="copy.attributes"/>
-        <xsl:apply-templates/>
-      </xsl:copy>
-    </xsl:when>
-    <xsl:otherwise>
-      <xsl:copy>
-        <xsl:call-template name="copy.attributes"/>
-        <info>
-          <xsl:apply-templates select="title" mode="copy"/>
-          <xsl:apply-templates select="titleabbrev" mode="copy"/>
-          <xsl:apply-templates select="subtitle" mode="copy"/>
-        </info>
-        <xsl:apply-templates/>
-      </xsl:copy>
-    </xsl:otherwise>
-  </xsl:choose>
-</xsl:template>
-
-<xsl:template match="sect1|sect2|sect3|sect4|sect5|section"
-             priority="200">
-  <section>
-    <xsl:call-template name="copy.attributes"/>
-
-    <xsl:if test="not(sect1info|sect2info|sect3info|sect4info|sect5info|sectioninfo)">
-      <info>
-        <xsl:apply-templates select="title" mode="copy"/>
-        <xsl:apply-templates select="titleabbrev" mode="copy"/>
-        <xsl:apply-templates select="subtitle" mode="copy"/>
-        <xsl:apply-templates select="abstract" mode="copy"/>
-      </info>
-    </xsl:if>
-    <xsl:apply-templates/>
-  </section>
-</xsl:template>
-
-<xsl:template match="simplesect"
-             priority="200">
-  <simplesect>
-    <xsl:call-template name="copy.attributes"/>
-    <info>
-      <xsl:apply-templates select="title" mode="copy"/>
-      <xsl:apply-templates select="titleabbrev" mode="copy"/>
-      <xsl:apply-templates select="subtitle" mode="copy"/>
-      <xsl:apply-templates select="abstract" mode="copy"/>
-    </info>
-    <xsl:apply-templates/>
-  </simplesect>
-</xsl:template>
-
-<xsl:template match="refsect1|refsect2|refsect3|refsection" priority="200">
-  <refsection>
-    <xsl:call-template name="copy.attributes"/>
-
-    <xsl:if test="not(refsect1info|refsect2info|refsect3info|refsectioninfo)">
-      <info>
-        <xsl:apply-templates select="title" mode="copy"/>
-        <xsl:apply-templates select="titleabbrev" mode="copy"/>
-        <xsl:apply-templates select="subtitle" mode="copy"/>
-        <xsl:apply-templates select="abstract" mode="copy"/>
-      </info>
-    </xsl:if>
-    <xsl:apply-templates/>
-  </refsection>
-</xsl:template>
-
-<xsl:template match="imagedata|videodata|audiodata|textdata" priority="200">
-  <xsl:copy>
-    <xsl:call-template name="copy.attributes">
-      <xsl:with-param name="suppress" select="'srccredit'"/>
-    </xsl:call-template>
-    <xsl:if test="@srccredit">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Check conversion of srccredit </xsl:text>
-          <xsl:text>(othercredit="srccredit").</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-      <info>
-        <othercredit class="other" otherclass="srccredit">
-          <orgname>???</orgname>
-          <contrib>
-            <xsl:value-of select="@srccredit"/>
-          </contrib>
-        </othercredit>
-      </info>
-    </xsl:if>
-  </xsl:copy>
-</xsl:template>
-
-<xsl:template match="sgmltag" priority="200">
-  <tag>
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:if test="@class = 'sgmlcomment'">
-      <xsl:attribute name="class">comment</xsl:attribute>
-    </xsl:if>
-    <xsl:apply-templates/>
-  </tag>
-</xsl:template>
-
-<xsl:template match="inlinegraphic[@format='linespecific']" priority="210">
-  <textobject>
-    <textdata>
-      <xsl:call-template name="copy.attributes"/>
-    </textdata>
-  </textobject>
-</xsl:template>
-
-<xsl:template match="inlinegraphic" priority="200">
-  <inlinemediaobject>
-    <imageobject>
-      <imagedata>
-       <xsl:call-template name="copy.attributes"/>
-      </imagedata>
-    </imageobject>
-  </inlinemediaobject>
-</xsl:template>
-
-<xsl:template match="graphic[@format='linespecific']" priority="210">
-  <mediaobject>
-    <textobject>
-      <textdata>
-       <xsl:call-template name="copy.attributes"/>
-      </textdata>
-    </textobject>
-  </mediaobject>
-</xsl:template>
-
-<xsl:template match="graphic" priority="200">
-  <mediaobject>
-    <imageobject>
-      <imagedata>
-       <xsl:call-template name="copy.attributes"/>
-      </imagedata>
-    </imageobject>
-  </mediaobject>
-</xsl:template>
-
-<xsl:template match="pubsnumber" priority="200">
-  <biblioid class="pubsnumber">
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </biblioid>
-</xsl:template>
-
-<xsl:template match="invpartnumber" priority="200">
-  <xsl:call-template name="emit-message">
-    <xsl:with-param name="message">
-      <xsl:text>Converting invpartnumber to biblioid otherclass="invpartnumber".</xsl:text>
-    </xsl:with-param>
-  </xsl:call-template>
-  <biblioid class="other" otherclass="invpartnumber">
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </biblioid>
-</xsl:template>
-
-<xsl:template match="contractsponsor" priority="200">
-  <xsl:variable name="contractnum"
-                select="preceding-sibling::contractnum|following-sibling::contractnum"/>
-
-  <xsl:call-template name="emit-message">
-    <xsl:with-param name="message">
-      <xsl:text>Converting contractsponsor to othercredit="contractsponsor".</xsl:text>
-    </xsl:with-param>
-  </xsl:call-template>
-
-  <othercredit class="other" otherclass="contractsponsor">
-    <orgname>
-      <xsl:call-template name="copy.attributes"/>
-      <xsl:apply-templates/>
-    </orgname>
-    <xsl:for-each select="$contractnum">
-      <contrib role="contractnum">
-        <xsl:apply-templates select="node()"/>
-      </contrib>
-    </xsl:for-each>
-  </othercredit>
-</xsl:template>
-
-<xsl:template match="contractnum" priority="200">
-  <xsl:if test="not(preceding-sibling::contractsponsor
-                    |following-sibling::contractsponsor)
-                and not(preceding-sibling::contractnum)">
-    <xsl:call-template name="emit-message">
-      <xsl:with-param name="message">
-        <xsl:text>Converting contractnum to othercredit="contractnum".</xsl:text>
-      </xsl:with-param>
-    </xsl:call-template>
-
-    <othercredit class="other" otherclass="contractnum">
-      <orgname>???</orgname>
-      <xsl:for-each select="self::contractnum
-                            |preceding-sibling::contractnum
-                            |following-sibling::contractnum">
-        <contrib>
-          <xsl:apply-templates select="node()"/>
-        </contrib>
-      </xsl:for-each>
-    </othercredit>
-  </xsl:if>
-</xsl:template>
-
-<xsl:template match="isbn|issn" priority="200">
-  <biblioid class="{local-name(.)}">
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </biblioid>
-</xsl:template>
-
-<xsl:template match="biblioid[count(*) = 1
-                             and ulink
-                             and normalize-space(text()) = '']" priority="200">
-  <biblioid xlink:href="{ulink/@url}">
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates select="ulink/node()"/>
-  </biblioid>
-</xsl:template>
-
-<xsl:template match="authorblurb" priority="200">
-  <personblurb>
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </personblurb>
-</xsl:template>
-
-<xsl:template match="collabname" priority="200">
-  <xsl:call-template name="emit-message">
-    <xsl:with-param name="message">
-      <xsl:text>Check conversion of collabname </xsl:text>
-      <xsl:text>(orgname role="collabname").</xsl:text>
-    </xsl:with-param>
-  </xsl:call-template>
-  <orgname role="collabname">
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </orgname>
-</xsl:template>
-
-<xsl:template match="modespec" priority="200">
-  <xsl:call-template name="emit-message">
-    <xsl:with-param name="message">
-      <xsl:text>Discarding modespec (</xsl:text>
-      <xsl:value-of select="."/>
-      <xsl:text>).</xsl:text>
-    </xsl:with-param>
-  </xsl:call-template>
-</xsl:template>
-
-<xsl:template match="mediaobjectco" priority="200">
-  <mediaobject>
-    <xsl:copy-of select="@*"/>
-    <xsl:apply-templates/>
-  </mediaobject>
-</xsl:template>
-
-<xsl:template match="remark" priority="200">
-  <!-- get rid of any embedded markup -->
-  <remark>
-    <xsl:copy-of select="@*"/>
-    <xsl:value-of select="."/>
-  </remark>
-</xsl:template>
-
-<xsl:template match="biblioentry/title
-                     |bibliomset/title
-                     |biblioset/title
-                     |bibliomixed/title" priority="400">
-  <citetitle>
-    <xsl:copy-of select="@*"/>
-    <xsl:apply-templates/>
-  </citetitle>
-</xsl:template>
-
-<xsl:template match="biblioentry/titleabbrev|biblioentry/subtitle
-                     |bibliomset/titleabbrev|bibliomset/subtitle
-                     |biblioset/titleabbrev|biblioset/subtitle
-                     |bibliomixed/titleabbrev|bibliomixed/subtitle"
-             priority="400">
-  <xsl:copy>
-    <xsl:copy-of select="@*"/>
-    <xsl:apply-templates/>
-  </xsl:copy>
-</xsl:template>
-
-<xsl:template match="biblioentry/contrib
-                     |bibliomset/contrib
-                     |bibliomixed/contrib" priority="200">
-  <xsl:call-template name="emit-message">
-    <xsl:with-param name="message">
-      <xsl:text>Check conversion of contrib </xsl:text>
-      <xsl:text>(othercontrib="contrib").</xsl:text>
-    </xsl:with-param>
-  </xsl:call-template>
-  <othercredit class="other" otherclass="contrib">
-    <orgname>???</orgname>
-    <contrib>
-      <xsl:call-template name="copy.attributes"/>
-      <xsl:apply-templates/>
-    </contrib>
-  </othercredit>
-</xsl:template>
-
-<xsl:template match="link" priority="200">
-  <xsl:copy>
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </xsl:copy>
-</xsl:template>
-
-<xsl:template match="ulink" priority="200">
-  <xsl:choose>
-    <xsl:when test="node()">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Converting ulink to link.</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-
-      <link xlink:href="{@url}">
-       <xsl:call-template name="copy.attributes">
-         <xsl:with-param name="suppress" select="'url'"/>
-       </xsl:call-template>
-       <xsl:apply-templates/>
-      </link>
-    </xsl:when>
-    <xsl:otherwise>
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Converting ulink to uri.</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-
-      <uri xlink:href="{@url}">
-       <xsl:call-template name="copy.attributes">
-         <xsl:with-param name="suppress" select="'url'"/>
-       </xsl:call-template>
-       <xsl:value-of select="@url"/>
-      </uri>
-    </xsl:otherwise>
-  </xsl:choose>
-</xsl:template>
-
-<xsl:template match="olink" priority="200">
-  <xsl:if test="@linkmode">
-    <xsl:call-template name="emit-message">
-      <xsl:with-param name="message">
-        <xsl:text>Discarding linkmode on olink.</xsl:text>
-      </xsl:with-param>
-    </xsl:call-template>
-  </xsl:if>
-
-  <xsl:choose>
-    <xsl:when test="@targetdocent">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Converting olink targetdocent to targetdoc.</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-
-      <olink targetdoc="{unparsed-entity-uri(@targetdocent)}">
-       <xsl:for-each select="@*">
-         <xsl:if test="name(.) != 'targetdocent'
-                       and name(.) != 'linkmode'">
-           <xsl:copy/>
-         </xsl:if>
-       </xsl:for-each>
-       <xsl:apply-templates/>
-      </olink>
-    </xsl:when>
-    <xsl:otherwise>
-      <olink>
-       <xsl:for-each select="@*">
-         <xsl:if test="name(.) != 'linkmode'">
-           <xsl:copy/>
-         </xsl:if>
-       </xsl:for-each>
-       <xsl:apply-templates/>
-      </olink>
-    </xsl:otherwise>
-  </xsl:choose>
-</xsl:template>
-
-<xsl:template match="biblioentry/firstname
-                     |biblioentry/surname
-                     |biblioentry/othername
-                     |biblioentry/lineage
-                     |biblioentry/honorific
-                     |bibliomset/firstname
-                     |bibliomset/surname
-                     |bibliomset/othername
-                     |bibliomset/lineage
-                     |bibliomset/honorific" priority="200">
-  <xsl:choose>
-    <xsl:when test="preceding-sibling::firstname
-                    |preceding-sibling::surname
-                    |preceding-sibling::othername
-                    |preceding-sibling::lineage
-                    |preceding-sibling::honorific">
-      <!-- nop -->
-    </xsl:when>
-    <xsl:otherwise>
-      <personname>
-        <xsl:apply-templates select="../firstname
-                                     |../surname
-                                     |../othername
-                                     |../lineage
-                                     |../honorific" mode="copy"/>
-      </personname>
-    </xsl:otherwise>
-  </xsl:choose>
-</xsl:template>
-
-<xsl:template match="areaset" priority="200">
-  <xsl:copy>
-    <xsl:call-template name="copy.attributes">
-      <xsl:with-param name="suppress" select="'coords'"/>
-    </xsl:call-template>
-    <xsl:apply-templates/>
-  </xsl:copy>
-</xsl:template>
-
-<xsl:template match="date|pubdate" priority="200">
-  <xsl:variable name="rp1" select="substring-before(normalize-space(.), ' ')"/>
-  <xsl:variable name="rp2"
-               select="substring-before(substring-after(normalize-space(.), ' '),
-                                        ' ')"/>
-  <xsl:variable name="rp3"
-               select="substring-after(substring-after(normalize-space(.), ' '), ' ')"/>
-
-  <xsl:variable name="p1">
-    <xsl:choose>
-      <xsl:when test="contains($rp1, ',')">
-       <xsl:value-of select="substring-before($rp1, ',')"/>
-      </xsl:when>
-      <xsl:otherwise>
-       <xsl:value-of select="$rp1"/>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:variable>
-
-  <xsl:variable name="p2">
-    <xsl:choose>
-      <xsl:when test="contains($rp2, ',')">
-       <xsl:value-of select="substring-before($rp2, ',')"/>
-      </xsl:when>
-      <xsl:otherwise>
-       <xsl:value-of select="$rp2"/>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:variable>
-
-  <xsl:variable name="p3">
-    <xsl:choose>
-      <xsl:when test="contains($rp3, ',')">
-       <xsl:value-of select="substring-before($rp3, ',')"/>
-      </xsl:when>
-      <xsl:otherwise>
-       <xsl:value-of select="$rp3"/>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:variable>
-
-  <xsl:variable name="date">
-    <xsl:choose>
-      <xsl:when test="string($p1+1) != 'NaN' and string($p3+1) != 'NaN'">
-       <xsl:choose>
-         <xsl:when test="$p2 = 'Jan' or $p2 = 'January'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-01-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Feb' or $p2 = 'February'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-02-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Mar' or $p2 = 'March'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-03-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Apr' or $p2 = 'April'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-04-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'May'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-05-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Jun' or $p2 = 'June'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-06-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Jul' or $p2 = 'July'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-07-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Aug' or $p2 = 'August'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-08-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Sep' or $p2 = 'September'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-09-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Oct' or $p2 = 'October'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-10-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Nov' or $p2 = 'November'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-11-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p2 = 'Dec' or $p2 = 'December'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-12-</xsl:text>
-           <xsl:number value="$p1" format="01"/>
-         </xsl:when>
-         <xsl:otherwise>
-           <xsl:apply-templates/>
-         </xsl:otherwise>
-       </xsl:choose>
-      </xsl:when>
-      <xsl:when test="string($p2+1) != 'NaN' and string($p3+1) != 'NaN'">
-       <xsl:choose>
-         <xsl:when test="$p1 = 'Jan' or $p1 = 'January'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-01-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Feb' or $p1 = 'February'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-02-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Mar' or $p1 = 'March'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-03-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Apr' or $p1 = 'April'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-04-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'May'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-05-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Jun' or $p1 = 'June'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-06-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Jul' or $p1 = 'July'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-07-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Aug' or $p1 = 'August'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-08-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Sep' or $p1 = 'September'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-09-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Oct' or $p1 = 'October'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-10-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Nov' or $p1 = 'November'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-11-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:when test="$p1 = 'Dec' or $p1 = 'December'">
-           <xsl:number value="$p3" format="0001"/>
-           <xsl:text>-12-</xsl:text>
-           <xsl:number value="$p2" format="01"/>
-         </xsl:when>
-         <xsl:otherwise>
-           <xsl:apply-templates/>
-         </xsl:otherwise>
-       </xsl:choose>
-      </xsl:when>
-      <xsl:otherwise>
-       <xsl:apply-templates/>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:variable>
-
-  <xsl:choose>
-    <xsl:when test="normalize-space($date) != normalize-space(.)">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Converted </xsl:text>
-          <xsl:value-of select="normalize-space(.)"/>
-          <xsl:text> into </xsl:text>
-          <xsl:value-of select="$date"/>
-          <xsl:text> for </xsl:text>
-          <xsl:value-of select="name(.)"/>
-        </xsl:with-param>
-      </xsl:call-template>
-
-      <xsl:copy>
-       <xsl:copy-of select="@*"/>
-       <xsl:value-of select="$date"/>
-      </xsl:copy>
-    </xsl:when>
-
-    <xsl:when test="$defaultDate != ''">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Unparseable date: </xsl:text>
-          <xsl:value-of select="normalize-space(.)"/>
-          <xsl:text> in </xsl:text>
-          <xsl:value-of select="name(.)"/>
-          <xsl:text> (Using default: </xsl:text>
-          <xsl:value-of select="$defaultDate"/>
-          <xsl:text>)</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-
-      <xsl:copy>
-       <xsl:copy-of select="@*"/>
-       <xsl:copy-of select="$defaultDate"/>
-       <xsl:comment>
-         <xsl:value-of select="."/>
-       </xsl:comment>
-      </xsl:copy>
-    </xsl:when>
-
-    <xsl:otherwise>
-      <!-- these don't really matter anymore
-           <xsl:call-template name="emit-message">
-           <xsl:with-param name="message">
-           <xsl:text>Unparseable date: </xsl:text>
-           <xsl:value-of select="normalize-space(.)"/>
-           <xsl:text> in </xsl:text>
-           <xsl:value-of select="name(.)"/>
-           </xsl:with-param>
-           </xsl:call-template>
-      -->
-      <xsl:copy>
-       <xsl:copy-of select="@*"/>
-       <xsl:apply-templates/>
-      </xsl:copy>
-    </xsl:otherwise>
-  </xsl:choose>      
-</xsl:template>
-
-<xsl:template match="title|subtitle|titleabbrev" priority="300">
-  <!-- nop -->
-</xsl:template>
-
-<xsl:template match="abstract" priority="300">
-  <xsl:if test="not(contains(name(parent::*),'info'))">
-    <xsl:call-template name="emit-message">
-      <xsl:with-param name="message">
-       <xsl:text>Check abstract; moved into info correctly?</xsl:text>
-      </xsl:with-param>
-    </xsl:call-template>
-  </xsl:if>
-</xsl:template>
-
-<xsl:template match="indexterm">
-  <!-- don't copy the defaulted significance='normal' attribute -->
-  <indexterm>
-    <xsl:call-template name="copy.attributes">
-      <xsl:with-param name="suppress">
-       <xsl:if test="@significance = 'normal'">significance</xsl:if>
-      </xsl:with-param>
-    </xsl:call-template>
-    <xsl:apply-templates/>
-  </indexterm>
-</xsl:template>
-
-<xsl:template match="ackno" priority="200">
-  <acknowledgements>
-    <xsl:copy-of select="@*"/>
-    <para>
-      <xsl:apply-templates/>
-    </para>
-  </acknowledgements>
-</xsl:template>
-
-<xsl:template match="lot|lotentry|tocback|tocchap|tocfront|toclevel1|
-                    toclevel2|toclevel3|toclevel4|toclevel5|tocpart" priority="200">
-  <tocdiv>
-    <xsl:copy-of select="@*"/>
-    <xsl:apply-templates/>
-  </tocdiv>
-</xsl:template>
-
-<xsl:template match="action" priority="200">
-  <phrase remap="action">
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </phrase>
-</xsl:template>
-
-<xsl:template match="beginpage" priority="200">
-  <xsl:comment> beginpage pagenum=<xsl:value-of select="@pagenum"/> </xsl:comment>
-  <xsl:call-template name="emit-message">
-    <xsl:with-param name="message">
-      <xsl:text>Replacing beginpage with comment</xsl:text>
-    </xsl:with-param>
-  </xsl:call-template>
-</xsl:template>
-
-<xsl:template match="structname|structfield" priority="200">
-  <varname remap="{local-name(.)}">
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </varname>
-</xsl:template>
-
-<!-- ====================================================================== -->
-
-<!-- 6 Feb 2008, ndw changed mode=copy so that it only copies the first level,
-     then it switches back to "normal" mode so that other rewriting templates
-     catch embedded fixes -->
-
-<!--
-<xsl:template match="ulink" priority="200" mode="copy">
-  <xsl:choose>
-    <xsl:when test="node()">
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Converting ulink to phrase.</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-
-      <phrase xlink:href="{@url}">
-       <xsl:call-template name="copy.attributes">
-         <xsl:with-param name="suppress" select="'url'"/>
-       </xsl:call-template>
-       <xsl:apply-templates/>
-      </phrase>
-    </xsl:when>
-    <xsl:otherwise>
-      <xsl:call-template name="emit-message">
-        <xsl:with-param name="message">
-          <xsl:text>Converting ulink to uri.</xsl:text>
-        </xsl:with-param>
-      </xsl:call-template>
-
-      <uri xlink:href="{@url}">
-       <xsl:call-template name="copy.attributes">
-         <xsl:with-param name="suppress" select="'url'"/>
-       </xsl:call-template>
-       <xsl:value-of select="@url"/>
-      </uri>
-    </xsl:otherwise>
-  </xsl:choose>
-</xsl:template>
-
-<xsl:template match="sgmltag" priority="200" mode="copy">
-  <tag>
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </tag>
-</xsl:template>
--->
-
-<xsl:template match="*" mode="copy">
-  <xsl:copy>
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </xsl:copy>
-</xsl:template>
-
-<!--
-<xsl:template match="comment()|processing-instruction()|text()" mode="copy">
-  <xsl:copy/>
-</xsl:template>
--->
-
-<!-- ====================================================================== -->
-
-<xsl:template match="*">
-  <xsl:copy>
-    <xsl:call-template name="copy.attributes"/>
-    <xsl:apply-templates/>
-  </xsl:copy>
-</xsl:template>
-
-<xsl:template match="comment()|processing-instruction()|text()">
-  <xsl:copy/>
-</xsl:template>
-
-<!-- ====================================================================== -->
-
-<xsl:template name="copy.attributes">
-  <xsl:param name="src" select="."/>
-  <xsl:param name="suppress" select="''"/>
-
-  <xsl:for-each select="$src/@*">
-    <xsl:choose>
-      <xsl:when test="local-name(.) = 'moreinfo'">
-        <xsl:call-template name="emit-message">
-          <xsl:with-param name="message">
-            <xsl:text>Discarding moreinfo on </xsl:text>
-            <xsl:value-of select="local-name($src)"/>
-          </xsl:with-param>
-        </xsl:call-template>
-      </xsl:when>
-      <xsl:when test="local-name(.) = 'lang'">
-        <xsl:attribute name="xml:lang">
-          <xsl:value-of select="."/>
-        </xsl:attribute>
-      </xsl:when>
-      <xsl:when test="local-name(.) = 'id'">
-        <xsl:attribute name="xml:id">
-          <xsl:value-of select="."/>
-        </xsl:attribute>
-      </xsl:when>
-      <xsl:when test="$suppress = local-name(.)"/>
-      <xsl:when test="local-name(.) = 'float'">
-       <xsl:choose>
-         <xsl:when test=". = '1'">
-            <xsl:call-template name="emit-message">
-              <xsl:with-param name="message">
-                <xsl:text>Discarding float on </xsl:text>
-                <xsl:value-of select="local-name($src)"/>
-              </xsl:with-param>
-            </xsl:call-template>
-            <xsl:if test="not($src/@floatstyle)">
-             <xsl:call-template name="emit-message">
-                <xsl:with-param name="message">
-                  <xsl:text>Adding floatstyle='normal' on </xsl:text>
-                  <xsl:value-of select="local-name($src)"/>
-                </xsl:with-param>
-              </xsl:call-template>
-              <xsl:attribute name="floatstyle">
-                <xsl:text>normal</xsl:text>
-             </xsl:attribute>
-           </xsl:if>
-         </xsl:when>
-         <xsl:when test=". = '0'">
-           <xsl:call-template name="emit-message">
-              <xsl:with-param name="message">
-                <xsl:text>Discarding float on </xsl:text>
-                <xsl:value-of select="local-name($src)"/>
-              </xsl:with-param>
-            </xsl:call-template>
-          </xsl:when>
-         <xsl:otherwise>
-           <xsl:call-template name="emit-message">
-          <xsl:with-param name="message">
-            <xsl:text>Discarding float on </xsl:text>
-            <xsl:value-of select="local-name($src)"/>
-          </xsl:with-param>
-            </xsl:call-template>
-            <xsl:if test="not($src/@floatstyle)">
-              <xsl:call-template name="emit-message">
-                <xsl:with-param name="message">
-                  <xsl:text>Adding floatstyle='</xsl:text>
-                  <xsl:value-of select="."/>
-                  <xsl:text>' on </xsl:text>
-                  <xsl:value-of select="local-name($src)"/>
-                </xsl:with-param>
-              </xsl:call-template>
-              <xsl:attribute name="floatstyle">
-               <xsl:value-of select="."/>
-             </xsl:attribute>
-           </xsl:if>
-         </xsl:otherwise>
-       </xsl:choose>
-      </xsl:when>
-      <xsl:when test="local-name(.) = 'entityref'">
-       <xsl:attribute name="fileref">
-         <xsl:value-of select="unparsed-entity-uri(@entityref)"/>
-       </xsl:attribute>
-      </xsl:when>
-
-      <xsl:when test="local-name($src) = 'simplemsgentry'
-                     and local-name(.) = 'audience'">
-        <xsl:attribute name="msgaud">
-          <xsl:value-of select="."/>
-        </xsl:attribute>
-      </xsl:when>
-      <xsl:when test="local-name($src) = 'simplemsgentry'
-                     and local-name(.) = 'origin'">
-        <xsl:attribute name="msgorig">
-          <xsl:value-of select="."/>
-        </xsl:attribute>
-      </xsl:when>
-      <xsl:when test="local-name($src) = 'simplemsgentry'
-                     and local-name(.) = 'level'">
-        <xsl:attribute name="msglevel">
-          <xsl:value-of select="."/>
-        </xsl:attribute>
-      </xsl:when>
-
-      <!-- * for upgrading XSL litprog params documentation -->
-      <xsl:when test="local-name($src) = 'refmiscinfo'
-                      and local-name(.) = 'role'
-                      and . = 'type'
-                      ">
-        <xsl:call-template name="emit-message">
-          <xsl:with-param name="message">
-            <xsl:text>Converting refmiscinfo@role=type to </xsl:text>
-            <xsl:text>@class=other,otherclass=type</xsl:text>
-          </xsl:with-param>
-        </xsl:call-template>
-        <xsl:attribute name="class">other</xsl:attribute>
-        <xsl:attribute name="otherclass">type</xsl:attribute>
-      </xsl:when>
-
-      <xsl:otherwise>
-        <xsl:copy/>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:for-each>
-</xsl:template>
-
-<!-- ====================================================================== -->
-
-<xsl:template match="*" mode="addNS">
-  <xsl:choose>
-    <xsl:when test="namespace-uri(.) = ''">
-      <xsl:element name="{local-name(.)}"
-                  namespace="http://docbook.org/ns/docbook">
-       <xsl:if test="not(parent::*)">
-         <xsl:attribute name="version">5.0</xsl:attribute>
-       </xsl:if>
-       <xsl:copy-of select="@*"/>
-       <xsl:apply-templates mode="addNS"/>
-      </xsl:element>
-    </xsl:when>
-    <xsl:otherwise>
-      <xsl:copy>
-       <xsl:if test="not(parent::*)">
-         <xsl:attribute name="version">5.0</xsl:attribute>
-       </xsl:if>
-       <xsl:copy-of select="@*"/>
-       <xsl:apply-templates mode="addNS"/>
-      </xsl:copy>
-    </xsl:otherwise>
-  </xsl:choose>
-</xsl:template>
-
-<xsl:template match="comment()|processing-instruction()|text()" mode="addNS">
-  <xsl:copy/>
-</xsl:template>
-
-<!-- ====================================================================== -->
-
-<xsl:template name="emit-message">
-  <xsl:param name="message"/>
-  <xsl:message>
-    <xsl:value-of select="$message"/>
-    <xsl:text> (</xsl:text>
-    <xsl:value-of select="$rootid"/>
-    <xsl:text>)</xsl:text>
-  </xsl:message>
-</xsl:template>
-
-</xsl:stylesheet>
diff --git a/org.argeo.app.core/src/org/argeo/app/odk/OdkUtils.java b/org.argeo.app.core/src/org/argeo/app/odk/OdkUtils.java
deleted file mode 100644 (file)
index 4d2f521..0000000
+++ /dev/null
@@ -1,193 +0,0 @@
-package org.argeo.app.odk;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
-
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.app.api.EntityMimeType;
-import org.argeo.app.api.EntityType;
-import org.argeo.cms.util.DigestUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.JcrxApi;
-
-/** Utilities around ODK. */
-public class OdkUtils {
-       private final static CmsLog log = CmsLog.getLog(OdkUtils.class);
-
-       public static Node loadOdkForm(Node formBase, String name, InputStream in, InputStream... additionalNodes)
-                       throws RepositoryException, IOException {
-               if (!formBase.isNodeType(EntityType.formSet.get()))
-                       throw new IllegalArgumentException(
-                                       "Parent path " + formBase + " must be of type " + EntityType.formSet.get());
-               Node form = JcrUtils.getOrAdd(formBase, name, OrxListName.xform.get(), NodeType.MIX_VERSIONABLE);
-
-               String previousCsum = JcrxApi.getChecksum(form, JcrxApi.MD5);
-               String previousFormId = Jcr.get(form, OrxListName.formID.get());
-               String previousFormVersion = Jcr.get(form, OrxListName.version.get());
-
-               Session s = formBase.getSession();
-               s.importXML(form.getPath(), in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-
-               for (InputStream additionalIn : additionalNodes) {
-                       s.importXML(form.getPath(), additionalIn, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-               }
-               s.save();
-
-               // manage instances
-               // NodeIterator instances =
-               // form.getNodes("h:html/h:head/xforms:model/xforms:instance");
-               NodeIterator instances = form.getNode("h:html/h:head/xforms:model").getNodes("xforms:instance");
-               Node primaryInstance = null;
-               while (instances.hasNext()) {
-                       Node instance = instances.nextNode();
-                       if (primaryInstance == null) {
-                               primaryInstance = instance;
-                       } else {// secondary instances
-                               String instanceId = instance.getProperty("id").getString();
-                               URI instanceUri = null;
-                               if (instance.hasProperty("src"))
-                                       try {
-                                               instanceUri = new URI(instance.getProperty("src").getString());
-                                       } catch (URISyntaxException e) {
-                                               throw new IllegalArgumentException("Instance " + instanceId + " has a badly formatted URI", e);
-                                       }
-                               if (instanceUri != null) {
-                                       if ("jr".equals(instanceUri.getScheme())) {
-                                               String uuid;
-                                               String mimeType;
-                                               String encoding = StandardCharsets.UTF_8.name();
-                                               String type = instanceUri.getHost();
-                                               String path = instanceUri.getPath();
-                                               if ("file".equals(type)) {
-                                                       if (!path.endsWith(".xml"))
-                                                               throw new IllegalArgumentException("File uri " + instanceUri + " must end with .xml");
-                                                       // Work around bug in ODK Collect not supporting paths
-                                                       // path = path.substring(0, path.length() - ".xml".length());
-                                                       // Node target = file.getSession().getNode(path);
-                                                       uuid = path.substring(1, path.length() - ".xml".length());
-                                                       mimeType = EntityMimeType.XML.getMimeType();
-                                               } else if ("file-csv".equals(type)) {
-                                                       if (!path.endsWith(".csv"))
-                                                               throw new IllegalArgumentException("File uri " + instanceUri + " must end with .csv");
-                                                       // Work around bug in ODK Collect not supporting paths
-                                                       // path = path.substring(0, path.length() - ".csv".length());
-                                                       // Node target = file.getSession().getNode(path);
-                                                       uuid = path.substring(1, path.length() - ".csv".length());
-                                                       mimeType = EntityMimeType.CSV.getMimeType();
-                                               } else {
-                                                       throw new IllegalArgumentException("Unsupported instance type " + type);
-                                               }
-                                               Node manifest = JcrUtils.getOrAdd(form, OrxManifestName.manifest.name(),
-                                                               OrxManifestName.manifest.get());
-                                               Node file = JcrUtils.getOrAdd(manifest, instanceId);
-                                               file.addMixin(NodeType.MIX_MIMETYPE);
-                                               file.setProperty(Property.JCR_MIMETYPE, mimeType);
-                                               file.setProperty(Property.JCR_ENCODING, encoding);
-                                               Node target = file.getSession().getNodeByIdentifier(uuid);
-
-//                                             if (target.isNodeType(NodeType.NT_QUERY)) {
-//                                                     Query query = target.getSession().getWorkspace().getQueryManager().getQuery(target);
-//                                                     query.setLimit(10);
-//                                                     QueryResult queryResult = query.execute();
-//                                                     RowIterator rit = queryResult.getRows();
-//                                                     while (rit.hasNext()) {
-//                                                             Row row = rit.nextRow();
-//                                                             for (Value value : row.getValues()) {
-//                                                                     System.out.print(value.getString());
-//                                                                     System.out.print(',');
-//                                                             }
-//                                                             System.out.print('\n');
-//                                                     }
-//
-//                                             }
-
-                                               if (target.isNodeType(NodeType.MIX_REFERENCEABLE)) {
-                                                       file.setProperty(Property.JCR_ID, target);
-                                                       if (file.hasProperty(Property.JCR_PATH))
-                                                               file.getProperty(Property.JCR_PATH).remove();
-                                               } else {
-                                                       file.setProperty(Property.JCR_PATH, target.getPath());
-                                                       if (file.hasProperty(Property.JCR_ID))
-                                                               file.getProperty(Property.JCR_ID).remove();
-                                               }
-                                       }
-                               }
-                       }
-               }
-
-               if (primaryInstance == null)
-                       throw new IllegalArgumentException("No primary instance found in " + form);
-               if (!primaryInstance.hasNodes())
-                       throw new IllegalArgumentException("No data found in primary instance of " + form);
-               NodeIterator primaryInstanceChildren = primaryInstance.getNodes();
-               Node data = primaryInstanceChildren.nextNode();
-               if (primaryInstanceChildren.hasNext())
-                       throw new IllegalArgumentException("More than one data found in primary instance of " + form);
-               String formId = data.getProperty("id").getString();
-               if (previousFormId != null && !formId.equals(previousFormId))
-                       log.warn("Form id of " + form + " changed from " + previousFormId + " to " + formId);
-               form.setProperty(OrxListName.formID.get(), formId);
-               String formVersion = data.getProperty("version").getString();
-
-               if (previousCsum == null)// save before checksuming
-                       s.save();
-               String newCsum;
-               try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
-                       s.exportDocumentView(form.getPath() + "/" + OdkNames.H_HTML, out, true, false);
-                       newCsum = DigestUtils.digest(DigestUtils.MD5, out.toByteArray());
-               }
-               if (previousCsum == null) {
-                       JcrxApi.addChecksum(form, newCsum);
-                       JcrUtils.updateLastModified(form);
-                       form.setProperty(OrxListName.version.get(), formVersion);
-                       s.save();
-                       s.getWorkspace().getVersionManager().checkpoint(form.getPath());
-                       if (log.isDebugEnabled())
-                               log.debug("New form " + form);
-               } else {
-                       if (newCsum.equals(previousCsum)) {
-                               // discard
-                               s.refresh(false);
-                               if (log.isDebugEnabled())
-                                       log.debug("Unmodified form " + form);
-                               return form;
-                       } else {
-                               if (formVersion.equals(previousFormVersion)) {
-                                       s.refresh(false);
-                                       throw new IllegalArgumentException("Form " + form + " has been changed but version " + formVersion
-                                                       + " has not been changed, discarding changes...");
-                               }
-                               form.setProperty(OrxListName.version.get(), formVersion);
-                               JcrxApi.addChecksum(form, newCsum);
-                               JcrUtils.updateLastModified(form);
-                               s.save();
-                               s.getWorkspace().getVersionManager().checkpoint(form.getPath());
-                               if (log.isDebugEnabled()) {
-                                       log.debug("Updated form " + form);
-                                       log.debug("Previous csum " + previousCsum);
-                                       log.debug("New csum " + newCsum);
-                               }
-                       }
-               }
-               return form;
-       }
-
-       /** Singleton. */
-       private OdkUtils() {
-
-       }
-
-}
index 0dff64c772ec9ec1f6a894fb203a370065fe252a..6fee2f96fefd53f7a36ca34264dc25a047de33cb 100644 (file)
@@ -1,7 +1,6 @@
 package org.argeo.app.xforms;
 
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
+import org.argeo.api.acr.Content;
 
 /** Called when a user has received a new form submission. */
 public interface FormSubmissionListener {
@@ -9,5 +8,5 @@ public interface FormSubmissionListener {
         * Called after a form submission has been stored in the user area. The
         * submission will be deleted if any exception is thrown.
         */
-       void formSubmissionReceived(Node node) throws RepositoryException;
+       void formSubmissionReceived(Content content);
 }
diff --git a/org.argeo.app.core/src/org/argeo/internal/app/core/AppUserStateImpl.java b/org.argeo.app.core/src/org/argeo/internal/app/core/AppUserStateImpl.java
deleted file mode 100644 (file)
index b7201a1..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.argeo.internal.app.core;
-
-import javax.jcr.Node;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.acr.ContentSession;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsSession;
-import org.argeo.app.api.AppUserState;
-import org.argeo.app.core.SuiteUtils;
-import org.argeo.cms.acr.ContentUtils;
-import org.argeo.cms.jcr.acr.JcrContentProvider;
-import org.argeo.jcr.Jcr;
-
-public class AppUserStateImpl implements AppUserState {
-       private JcrContentProvider jcrContentProvider;
-
-       @Override
-       public Content getOrCreateSessionDir(ContentSession contentSession, CmsSession session) {
-               Node userDirNode = jcrContentProvider.doInAdminSession((adminSession) -> {
-                       Node node = SuiteUtils.getOrCreateCmsSessionNode(adminSession, session);
-                       return node;
-               });
-               Content userDir = contentSession
-                               .get(ContentUtils.SLASH + CmsConstants.SYS_WORKSPACE + Jcr.getPath(userDirNode));
-               return userDir;
-       }
-
-       public void setJcrContentProvider(JcrContentProvider jcrContentProvider) {
-               this.jcrContentProvider = jcrContentProvider;
-       }
-
-}
diff --git a/org.argeo.app.jcr/.classpath b/org.argeo.app.jcr/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.argeo.app.jcr/.project b/org.argeo.app.jcr/.project
new file mode 100644 (file)
index 0000000..f688a62
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.app.jcr</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.app.jcr/OSGI-INF/appUserState.xml b/org.argeo.app.jcr/OSGI-INF/appUserState.xml
new file mode 100644 (file)
index 0000000..5dc205e
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.internal.app.jcr.appUserState">
+   <implementation class="org.argeo.internal.app.jcr.AppUserStateImpl"/>
+   <service>
+      <provide interface="org.argeo.app.api.AppUserState"/>
+   </service>
+   <reference bind="setJcrContentProvider" cardinality="1..1" interface="org.argeo.cms.jcr.acr.JcrContentProvider" name="JcrContentProvider" policy="static"/>
+</scr:component>
diff --git a/org.argeo.app.jcr/OSGI-INF/maintenanceService.xml b/org.argeo.app.jcr/OSGI-INF/maintenanceService.xml
new file mode 100644 (file)
index 0000000..7167ff6
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="true"  name="Suite Maintenance Service">
+   <implementation class="org.argeo.internal.app.jcr.SuiteMaintenanceService"/>
+   <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static" target="(cn=ego)"/>
+   <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.api.cms.transaction.WorkTransaction" name="WorkTransaction" policy="static"/>
+   <reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
+   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.spi.ProvidedRepository" name="ContentRepository" policy="static"/>
+</scr:component>
diff --git a/org.argeo.app.jcr/OSGI-INF/termsManager.xml b/org.argeo.app.jcr/OSGI-INF/termsManager.xml
new file mode 100644 (file)
index 0000000..e6a2abd
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="true" name="Suite Terms Manager">
+   <implementation class="org.argeo.app.jcr.terms.SuiteTermsManager"/>
+   <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static" target="(cn=ego)"/>
+   <service>
+      <provide interface="org.argeo.app.api.TermsManager"/>
+   </service>
+</scr:component>
diff --git a/org.argeo.app.jcr/bnd.bnd b/org.argeo.app.jcr/bnd.bnd
new file mode 100644 (file)
index 0000000..b190cf4
--- /dev/null
@@ -0,0 +1,11 @@
+Service-Component:\
+OSGI-INF/termsManager.xml,\
+OSGI-INF/maintenanceService.xml,\
+OSGI-INF/appUserState.xml,\
+
+Import-Package:\
+javax.jcr.nodetype,\
+javax.jcr.security,\
+org.apache.jackrabbit.*;version="[1,4)",\
+org.argeo.cms.acr,\
+*
\ No newline at end of file
diff --git a/org.argeo.app.jcr/build.properties b/org.argeo.app.jcr/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/CustomMaintenanceService.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/CustomMaintenanceService.java
new file mode 100644 (file)
index 0000000..9bb72de
--- /dev/null
@@ -0,0 +1,75 @@
+package org.argeo.app.jcr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.ItemExistsException;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.app.api.EntityType;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.maintenance.AbstractMaintenanceService;
+
+/** Base for custom initialisations. */
+public abstract class CustomMaintenanceService extends AbstractMaintenanceService {
+       private final static CmsLog log = CmsLog.getLog(AbstractMaintenanceService.class);
+
+       protected List<String> getTypologies() {
+               return new ArrayList<>();
+       }
+
+       protected String getTypologiesLoadBase() {
+               return "";
+       }
+
+       protected void loadTypologies(Node customBaseNode) throws RepositoryException, IOException {
+               List<String> typologies = getTypologies();
+               if (!typologies.isEmpty()) {
+                       Node termsBase = JcrUtils.getOrAdd(customBaseNode, EntityType.terms.name(), EntityType.typologies.get());
+                       for (String terms : typologies) {
+                               loadTerms(termsBase, terms);
+                       }
+                       // TODO do not save here, so that upper layers can decide when to save
+                       termsBase.getSession().save();
+               }
+       }
+
+       protected void loadTerms(Node termsBase, String name) throws IOException, RepositoryException {
+               try {
+//                     if (termsBase.hasNode(name))
+//                             return;
+                       String typologiesLoadBase = getTypologiesLoadBase();
+                       if (typologiesLoadBase.contains("/") && !typologiesLoadBase.endsWith("/"))
+                               typologiesLoadBase = typologiesLoadBase + "/";
+                       String termsLoadPath = typologiesLoadBase + name + ".xml";
+                       URL termsUrl = getClass().getResource(termsLoadPath);
+                       if (termsUrl == null)
+                               throw new IllegalArgumentException("Terms '" + name + "' not found.");
+                       try (InputStream in = termsUrl.openStream()) {
+                               termsBase.getSession().importXML(termsBase.getPath(), in,
+                                               ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+                       } catch (ItemExistsException e) {
+                               log.warn("Terms " + name + " exists with another UUID, removing it...");
+                               termsBase.getNode(name).remove();
+                               try (InputStream in = termsUrl.openStream()) {
+                                       termsBase.getSession().importXML(termsBase.getPath(), in,
+                                                       ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+                               }
+                       }
+                       if (log.isDebugEnabled())
+                               log.debug("Terms '" + name + "' loaded.");
+                       // TODO do not save here, so that upper layers can decide when to save
+                       termsBase.getSession().save();
+               } catch (RepositoryException | IOException e) {
+                       log.error("Cannot load terms '" + name + "': " + e.getMessage());
+                       throw e;
+               }
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/JcrEntityDefinition.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/JcrEntityDefinition.java
new file mode 100644 (file)
index 0000000..e76315c
--- /dev/null
@@ -0,0 +1,73 @@
+package org.argeo.app.jcr;
+
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.app.api.EntityConstants;
+import org.argeo.app.api.EntityDefinition;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.jcr.Jcr;
+import org.osgi.framework.BundleContext;
+
+/** An entity definition based on a JCR data structure. */
+public class JcrEntityDefinition implements EntityDefinition {
+       private Repository repository;
+
+       private String type;
+       private String defaultEditorId;
+
+       public void init(BundleContext bundleContext, Map<String, String> properties) throws RepositoryException {
+               Session adminSession = CmsJcrUtils.openDataAdminSession(repository, null);
+               try {
+                       type = properties.get(EntityConstants.TYPE);
+                       if (type == null)
+                               throw new IllegalArgumentException("Entity type property " + EntityConstants.TYPE + " must be set.");
+                       defaultEditorId = properties.get(EntityConstants.DEFAULT_EDITOR_ID);
+//                     String definitionPath = EntityNames.ENTITY_DEFINITIONS_PATH + '/' + type;
+//                     if (!adminSession.itemExists(definitionPath)) {
+//                             Node entityDefinition = JcrUtils.mkdirs(adminSession, definitionPath, EntityTypes.ENTITY_DEFINITION);
+////                           entityDefinition.addMixin(EntityTypes.ENTITY_DEFINITION);
+//                             adminSession.save();
+//                     }
+                       initJcr(adminSession);
+               } finally {
+                       Jcr.logout(adminSession);
+               }
+       }
+
+       /** To be overridden in order to perform additional initialisations. */
+       protected void initJcr(Session adminSession) throws RepositoryException {
+
+       }
+
+       public void destroy(BundleContext bundleContext, Map<String, String> properties) throws RepositoryException {
+
+       }
+
+       @Override
+       public String getEditorId(Node entity) {
+               return defaultEditorId;
+       }
+
+       @Override
+       public String getType() {
+               return type;
+       }
+
+       protected Repository getRepository() {
+               return repository;
+       }
+
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+       public String toString() {
+               return "Entity Definition " + getType();
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/SuiteJcrUtils.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/SuiteJcrUtils.java
new file mode 100644 (file)
index 0000000..49b7ede
--- /dev/null
@@ -0,0 +1,114 @@
+package org.argeo.app.jcr;
+
+import static org.argeo.app.core.SuiteUtils.USER_DEVICES_NODE_NAME;
+import static org.argeo.app.core.SuiteUtils.USER_SESSIONS_NODE_NAME;
+import static org.argeo.app.core.SuiteUtils.USER_STATE_NODE_NAME;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.security.Privilege;
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.app.api.AppUserState;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.core.SuiteUtils;
+import org.argeo.cms.RoleNameUtils;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+
+/** JCR utilities. */
+public class SuiteJcrUtils {
+       /** @deprecated Use {@link AppUserState} instead. */
+       @Deprecated
+       public static Node getOrCreateUserNode(Session adminSession, String userDn) {
+               try {
+                       Node usersBase = adminSession.getNode(EntityType.user.basePath());
+                       String uid = RoleNameUtils.getLastRdnValue(userDn);
+                       Node userNode;
+                       if (!usersBase.hasNode(uid)) {
+                               userNode = usersBase.addNode(uid, NodeType.NT_UNSTRUCTURED);
+                               userNode.addMixin(EntityType.user.get());
+                               userNode.addMixin(NodeType.MIX_CREATED);
+                               userNode.setProperty(LdapAttr.distinguishedName.get(), userDn.toString());
+                               userNode.setProperty(LdapAttr.uid.get(), uid);
+                       } else {
+                               userNode = usersBase.getNode(uid);
+                       }
+
+                       if (!userNode.hasNode(USER_SESSIONS_NODE_NAME)) {
+                               // Migrate existing user node
+                               Node sessionsNode = userNode.addNode(USER_SESSIONS_NODE_NAME, NodeType.NT_UNSTRUCTURED);
+                               oldSessions: for (NodeIterator nit = userNode.getNodes(); nit.hasNext();) {
+                                       Node child = nit.nextNode();
+                                       if (USER_SESSIONS_NODE_NAME.equals(child.getName()) || child.getName().startsWith("rep:")
+                                                       || child.getName().startsWith("jcr:"))
+                                               continue oldSessions;
+                                       Node target = sessionsNode.addNode(child.getName());
+                                       JcrUtils.copy(child, target);
+                               }
+
+                               Node userStateNode = userNode.addNode(USER_STATE_NODE_NAME, NodeType.NT_UNSTRUCTURED);
+                               Node userDevicesNode = userNode.addNode(USER_DEVICES_NODE_NAME, NodeType.NT_UNSTRUCTURED);
+
+                               adminSession.save();
+//                             JackrabbitSecurityUtils.denyPrivilege(adminSession, userNode.getPath(), SuiteRole.coworker.dn(),
+//                                             Privilege.JCR_READ);
+                               JcrUtils.addPrivilege(adminSession, userNode.getPath(), new X500Principal(userDn.toString()).getName(),
+                                               Privilege.JCR_READ);
+                               JcrUtils.addPrivilege(adminSession, userNode.getPath(), CmsConstants.ROLE_USER_ADMIN,
+                                               Privilege.JCR_ALL);
+
+                               JcrUtils.addPrivilege(adminSession, userStateNode.getPath(), userDn, Privilege.JCR_ALL);
+                               JcrUtils.addPrivilege(adminSession, userDevicesNode.getPath(), userDn, Privilege.JCR_ALL);
+                       }
+                       return userNode;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot create user node for " + userDn, e);
+               }
+       }
+
+       /** @deprecated Use {@link AppUserState} instead. */
+       @Deprecated
+       public static Node getCmsSessionNode(Session session, CmsSession cmsSession) {
+               try {
+                       return session.getNode(SuiteUtils.getUserNodePath(cmsSession.getUserDn()) + '/' + USER_SESSIONS_NODE_NAME + '/'
+                                       + cmsSession.getUuid().toString());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get session dir for " + cmsSession, e);
+               }
+       }
+
+       /** @deprecated Use {@link AppUserState} instead. */
+       @Deprecated
+       public static Node getOrCreateCmsSessionNode(Session adminSession, CmsSession cmsSession) {
+               try {
+                       String userDn = cmsSession.getUserDn();
+                       Node userNode = getOrCreateUserNode(adminSession, userDn);
+                       Node sessionsNode = userNode.getNode(USER_SESSIONS_NODE_NAME);
+                       String cmsSessionUuid = cmsSession.getUuid().toString();
+                       Node cmsSessionNode;
+                       if (!sessionsNode.hasNode(cmsSessionUuid)) {
+                               cmsSessionNode = sessionsNode.addNode(cmsSessionUuid, NodeType.NT_UNSTRUCTURED);
+                               cmsSessionNode.addMixin(NodeType.MIX_CREATED);
+                               adminSession.save();
+                               JcrUtils.addPrivilege(adminSession, cmsSessionNode.getPath(), cmsSession.getUserRole(),
+                                               Privilege.JCR_ALL);
+                       } else {
+                               cmsSessionNode = sessionsNode.getNode(cmsSessionUuid);
+                       }
+                       return cmsSessionNode;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot create session dir for " + cmsSession, e);
+               }
+       }
+
+       /** singleton */
+       private SuiteJcrUtils() {
+       }
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/XPathUtils.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/XPathUtils.java
new file mode 100644 (file)
index 0000000..2c3babe
--- /dev/null
@@ -0,0 +1,175 @@
+package org.argeo.app.jcr;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+
+import org.apache.jackrabbit.util.ISO9075;
+import org.argeo.api.cms.CmsLog;
+
+/** Ease XPath generation for JCR requests */
+public class XPathUtils {
+       private final static CmsLog log = CmsLog.getLog(XPathUtils.class);
+
+       private final static String QUERY_XPATH = "xpath";
+
+       public static String descendantFrom(String parentPath) {
+               if (notEmpty(parentPath)) {
+                       if ("/".equals(parentPath))
+                               parentPath = "";
+                       // Hardcoded dependency to Jackrabbit. Remove
+                       String result = "/jcr:root" + ISO9075.encodePath(parentPath);
+                       if (log.isTraceEnabled()) {
+                               String result2 = "/jcr:root" + parentPath;
+                               if (!result2.equals(result))
+                                       log.warn("Encoded Path " + result2 + " --> " + result);
+                       }
+                       return result;
+               } else
+                       return "";
+       }
+
+       public static String localAnd(String... conditions) {
+               StringBuilder builder = new StringBuilder();
+               for (String condition : conditions) {
+                       if (notEmpty(condition)) {
+                               builder.append(" ").append(condition).append(" and ");
+                       }
+               }
+               if (builder.length() > 3)
+                       return builder.substring(0, builder.length() - 4);
+               else
+                       return "";
+       }
+
+       public static String xPathNot(String condition) {
+               if (notEmpty(condition))
+                       return "not(" + condition + ")";
+               else
+                       return "";
+       }
+
+       public static String getFreeTextConstraint(String filter) throws RepositoryException {
+               StringBuilder builder = new StringBuilder();
+               if (notEmpty(filter)) {
+                       String[] strs = filter.trim().split(" ");
+                       for (String token : strs) {
+                               builder.append("jcr:contains(.,'*" + encodeXPathStringValue(token) + "*') and ");
+                       }
+                       return builder.substring(0, builder.length() - 4);
+               }
+               return "";
+       }
+
+       public static String getPropertyContains(String propertyName, String filter) throws RepositoryException {
+               if (notEmpty(filter))
+                       return "jcr:contains(@" + propertyName + ",'*" + encodeXPathStringValue(filter) + "*')";
+               return "";
+       }
+
+       private final static DateFormat jcrRefFormatter = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSS'+02:00'");
+
+       /**
+        * @param propertyName
+        * @param calendar       the reference date
+        * @param lowerOrGreater "&lt;", "&gt;" TODO validate "&gt;="
+        * @throws RepositoryException
+        */
+       public static String getPropertyDateComparaison(String propertyName, Calendar cal, String lowerOrGreater)
+                       throws RepositoryException {
+               if (cal != null) {
+                       String jcrDateStr = jcrRefFormatter.format(cal.getTime());
+
+                       // jcrDateStr = "2015-08-03T05:00:03:000Z";
+                       String result = "@" + propertyName + " " + lowerOrGreater + " xs:dateTime('" + jcrDateStr + "')";
+                       return result;
+               }
+               return "";
+       }
+
+       public static String getPropertyEquals(String propertyName, String value) {
+               if (notEmpty(value))
+                       return "@" + propertyName + "='" + encodeXPathStringValue(value) + "'";
+               return "";
+       }
+
+       public static String encodeXPathStringValue(String propertyValue) {
+               // TODO implement safer mechanism to escape invalid characters
+               // Also check why we have used this regex in ResourceSerrviceImpl l 474
+               // String cleanedKey = key.replaceAll("(?:')", "''");
+               String result = propertyValue.replaceAll("'", "''");
+               return result;
+       }
+
+       public static void andAppend(StringBuilder builder, String condition) {
+               if (notEmpty(condition)) {
+                       builder.append(condition);
+                       builder.append(" and ");
+               }
+       }
+
+       public static void appendOrderByProperties(StringBuilder builder, boolean ascending, String... propertyNames) {
+               if (propertyNames.length > 0) {
+                       builder.append(" order by ");
+                       for (String propName : propertyNames)
+                               builder.append("@").append(propName).append(", ");
+                       builder = builder.delete(builder.length() - 2, builder.length());
+                       if (ascending)
+                               builder.append(" ascending ");
+                       else
+                               builder.append(" descending ");
+               }
+       }
+
+       public static void appendAndPropStringCondition(StringBuilder builder, String propertyName, String filter)
+                       throws RepositoryException {
+               if (notEmpty(filter)) {
+                       andAppend(builder, getPropertyContains(propertyName, filter));
+               }
+       }
+
+       public static void appendAndNotPropStringCondition(StringBuilder builder, String propertyName, String filter)
+                       throws RepositoryException {
+               if (notEmpty(filter)) {
+                       String cond = getPropertyContains(propertyName, filter);
+                       builder.append(xPathNot(cond));
+                       builder.append(" and ");
+               }
+       }
+
+       public static Query createQuery(Session session, String queryString) throws RepositoryException {
+               QueryManager queryManager = session.getWorkspace().getQueryManager();
+               // Localise JCR properties for XPATH
+               queryString = localiseJcrItemNames(queryString);
+               return queryManager.createQuery(queryString, QUERY_XPATH);
+       }
+
+       private final static String NS_JCR = "\\{http://www.jcp.org/jcr/1.0\\}";
+       private final static String NS_NT = "\\{http://www.jcp.org/jcr/nt/1.0\\}";
+       private final static String NS_MIX = "\\{http://www.jcp.org/jcr/mix/1.0\\}";
+
+       /**
+        * Replace the generic namespace with the local "jcr:", "nt:", "mix:" values. It
+        * is a workaround that must be later cleaned
+        */
+       public static String localiseJcrItemNames(String name) {
+               name = name.replaceAll(NS_JCR, "jcr:");
+               name = name.replaceAll(NS_NT, "nt:");
+               name = name.replaceAll(NS_MIX, "mix:");
+               return name;
+       }
+
+       private static boolean notEmpty(String stringToTest) {
+               return !(stringToTest == null || "".equals(stringToTest.trim()));
+       }
+
+       /** Singleton. */
+       private XPathUtils() {
+
+       }
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/Dbk4Converter.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/Dbk4Converter.java
new file mode 100644 (file)
index 0000000..60f2928
--- /dev/null
@@ -0,0 +1,103 @@
+package org.argeo.app.jcr.docbook;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Templates;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.argeo.jcr.JcrException;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import net.sf.saxon.BasicTransformerFactory;
+import net.sf.saxon.TransformerFactoryImpl;
+
+/** Convert from DocBook v4 to DocBook v5, using the official XSL. */
+public class Dbk4Converter {
+       private final Templates templates;
+
+       public Dbk4Converter() {
+               try (InputStream in = getClass().getResourceAsStream("db4-upgrade.xsl")) {
+                       Source xsl = new StreamSource(in);
+                       TransformerFactory transformerFactory = new BasicTransformerFactory();
+//                     TransformerFactory transformerFactory = new TransformerFactoryImpl();
+                       templates = transformerFactory.newTemplates(xsl);
+               } catch (IOException | TransformerConfigurationException e) {
+                       throw new RuntimeException("Cannot initialise DocBook v4 converter", e);
+               }
+       }
+
+       public void importXml(Node baseNode, InputStream in) throws IOException {
+               try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
+                       DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+                       factory.setXIncludeAware(true);
+                       factory.setNamespaceAware(true);
+                       DocumentBuilder docBuilder = factory.newDocumentBuilder();
+                       Document doc = docBuilder.parse(new InputSource(in));
+                       Source xmlInput = new DOMSource(doc);
+
+//                     ContentHandler contentHandler = baseNode.getSession().getImportContentHandler(baseNode.getPath(),
+//                                     ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+
+                       Transformer transformer = templates.newTransformer();
+                       Result xmlOutput = new StreamResult(out);
+                       transformer.transform(xmlInput, xmlOutput);
+                       try (InputStream dbk5in = new ByteArrayInputStream(out.toByteArray())) {
+                               baseNode.getSession().importXML(baseNode.getPath(), dbk5in,
+                                               ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot import XML to " + baseNode, e);
+               } catch (TransformerException | SAXException | ParserConfigurationException e) {
+                       throw new RuntimeException("Cannot import DocBook v4 to " + baseNode, e);
+               }
+
+       }
+
+       public static void main(String[] args) {
+               try {
+
+                       Source xsl = new StreamSource(new File("/usr/share/xml/docbook5/stylesheet/upgrade/db4-upgrade.xsl"));
+                       TransformerFactory transformerFactory = new TransformerFactoryImpl();
+                       Templates templates = transformerFactory.newTemplates(xsl);
+
+                       File inputDir = new File(args[0]);
+                       File outputDir = new File(args[1]);
+
+                       for (File inputFile : inputDir.listFiles()) {
+                               Result xmlOutput = new StreamResult(new File(outputDir, inputFile.getName()));
+
+                               DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+                               factory.setXIncludeAware(true);
+                               factory.setNamespaceAware(true);
+                               DocumentBuilder docBuilder = factory.newDocumentBuilder();
+                               Document doc = docBuilder.parse(inputFile);
+                               Source xmlInput = new DOMSource(doc);
+                               Transformer transformer = templates.newTransformer();
+                               transformer.transform(xmlInput, xmlOutput);
+                       }
+               } catch (Throwable e) {
+                       e.printStackTrace();
+               }
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/DbkJcrUtils.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/DbkJcrUtils.java
new file mode 100644 (file)
index 0000000..4454905
--- /dev/null
@@ -0,0 +1,253 @@
+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() {
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/db4-upgrade.xsl b/org.argeo.app.jcr/src/org/argeo/app/jcr/docbook/db4-upgrade.xsl
new file mode 100644 (file)
index 0000000..00096be
--- /dev/null
@@ -0,0 +1,1398 @@
+<?xml version="1.0"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:exsl="http://exslt.org/common"
+               xmlns:db = "http://docbook.org/ns/docbook"
+               xmlns:xlink="http://www.w3.org/1999/xlink"
+                exclude-result-prefixes="exsl db"
+                version="1.0">
+
+<!--
+# ======================================================================
+# This file is part of DocBook V5.0CR5
+#
+# Copyright 2005 Norman Walsh, Sun Microsystems, Inc., and the
+# Organization for the Advancement of Structured Information
+# Standards (OASIS).
+#
+# Release: $Id: db4-upgrade.xsl 7660 2008-02-06 13:48:36Z nwalsh $
+#
+# Permission to use, copy, modify and distribute this stylesheet
+# and its accompanying documentation for any purpose and without fee
+# is hereby granted in perpetuity, provided that the above copyright
+# notice and this paragraph appear in all copies. The copyright
+# holders make no representation about the suitability of the schema
+# for any purpose. It is provided "as is" without expressed or implied
+# warranty.
+#
+# Please direct all questions, bug reports, or suggestions for changes
+# to the docbook@lists.oasis-open.org mailing list. For more
+# information, see http://www.oasis-open.org/docbook/.
+#
+# ======================================================================
+-->
+
+<xsl:variable name="version" select="'1.0'"/>
+
+<xsl:output method="xml" encoding="utf-8" indent="no" omit-xml-declaration="yes"/>
+
+<xsl:preserve-space elements="*"/>
+<xsl:param name="rootid">
+  <xsl:choose>
+  <xsl:when test="/*/@id">
+    <xsl:value-of select="/*/@id"/>
+  </xsl:when>
+  <xsl:otherwise>
+    <xsl:text>UNKNOWN</xsl:text>
+  </xsl:otherwise>
+  </xsl:choose>
+</xsl:param>
+
+<xsl:param name="defaultDate" select="''"/>
+
+<xsl:template match="/">
+  <xsl:variable name="converted">
+    <xsl:apply-templates/>
+  </xsl:variable>
+  <xsl:comment>
+    <xsl:text> Converted by db4-upgrade version </xsl:text>
+    <xsl:value-of select="$version"/>
+    <xsl:text> </xsl:text>
+  </xsl:comment>
+  <xsl:text>&#10;</xsl:text>
+  <xsl:apply-templates select="exsl:node-set($converted)/*" mode="addNS"/>
+</xsl:template>
+
+<xsl:template match="bookinfo|chapterinfo|articleinfo|artheader|appendixinfo
+                    |blockinfo
+                     |bibliographyinfo|glossaryinfo|indexinfo|setinfo
+                    |setindexinfo
+                     |sect1info|sect2info|sect3info|sect4info|sect5info
+                     |sectioninfo
+                     |refsect1info|refsect2info|refsect3info|refsectioninfo
+                    |referenceinfo|partinfo"
+              priority="200">
+  <info>
+    <xsl:call-template name="copy.attributes"/>
+
+    <!-- titles can be inside or outside or both. fix that -->
+    <xsl:choose>
+      <xsl:when test="title and following-sibling::title">
+        <xsl:if test="title != following-sibling::title">
+          <xsl:call-template name="emit-message">
+            <xsl:with-param name="message">
+              <xsl:text>Check </xsl:text>
+              <xsl:value-of select="name(..)"/>
+              <xsl:text> title.</xsl:text>
+            </xsl:with-param>
+          </xsl:call-template>
+        </xsl:if>
+        <xsl:apply-templates select="title" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="title">
+        <xsl:apply-templates select="title" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="following-sibling::title">
+        <xsl:apply-templates select="following-sibling::title" mode="copy"/>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:call-template name="emit-message">
+          <xsl:with-param name="message">
+            <xsl:text>Check </xsl:text>
+            <xsl:value-of select="name(..)"/>
+            <xsl:text>: no title.</xsl:text>
+          </xsl:with-param>
+        </xsl:call-template>
+      </xsl:otherwise>
+    </xsl:choose>
+
+    <xsl:choose>
+      <xsl:when test="titleabbrev and following-sibling::titleabbrev">
+        <xsl:if test="titleabbrev != following-sibling::titleabbrev">
+          <xsl:call-template name="emit-message">
+            <xsl:with-param name="message">
+              <xsl:text>Check </xsl:text>
+              <xsl:value-of select="name(..)"/>
+              <xsl:text> titleabbrev.</xsl:text>
+            </xsl:with-param>
+          </xsl:call-template>
+        </xsl:if>
+        <xsl:apply-templates select="titleabbrev" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="titleabbrev">
+        <xsl:apply-templates select="titleabbrev" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="following-sibling::titleabbrev">
+        <xsl:apply-templates select="following-sibling::titleabbrev" mode="copy"/>
+      </xsl:when>
+    </xsl:choose>
+
+    <xsl:choose>
+      <xsl:when test="subtitle and following-sibling::subtitle">
+        <xsl:if test="subtitle != following-sibling::subtitle">
+          <xsl:call-template name="emit-message">
+            <xsl:with-param name="message">
+              <xsl:text>Check </xsl:text>
+              <xsl:value-of select="name(..)"/>
+              <xsl:text> subtitle.</xsl:text>
+            </xsl:with-param>
+          </xsl:call-template>
+        </xsl:if>
+        <xsl:apply-templates select="subtitle" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="subtitle">
+        <xsl:apply-templates select="subtitle" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="following-sibling::subtitle">
+        <xsl:apply-templates select="following-sibling::subtitle" mode="copy"/>
+      </xsl:when>
+    </xsl:choose>
+
+    <xsl:apply-templates/>
+  </info>
+</xsl:template>
+
+<xsl:template match="objectinfo|prefaceinfo|refsynopsisdivinfo
+                    |screeninfo|sidebarinfo"
+             priority="200">
+  <info>
+    <xsl:call-template name="copy.attributes"/>
+
+    <!-- titles can be inside or outside or both. fix that -->
+    <xsl:choose>
+      <xsl:when test="title and following-sibling::title">
+        <xsl:if test="title != following-sibling::title">
+          <xsl:call-template name="emit-message">
+            <xsl:with-param name="message">
+              <xsl:text>Check </xsl:text>
+              <xsl:value-of select="name(..)"/>
+              <xsl:text> title.</xsl:text>
+            </xsl:with-param>
+          </xsl:call-template>
+        </xsl:if>
+        <xsl:apply-templates select="title" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="title">
+        <xsl:apply-templates select="title" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="following-sibling::title">
+        <xsl:apply-templates select="following-sibling::title" mode="copy"/>
+      </xsl:when>
+      <xsl:otherwise>
+       <!-- it's ok if there's no title on these -->
+      </xsl:otherwise>
+    </xsl:choose>
+
+    <xsl:choose>
+      <xsl:when test="titleabbrev and following-sibling::titleabbrev">
+        <xsl:if test="titleabbrev != following-sibling::titleabbrev">
+          <xsl:call-template name="emit-message">
+          <xsl:with-param name="message">
+            <xsl:text>Check </xsl:text>
+            <xsl:value-of select="name(..)"/>
+            <xsl:text> titleabbrev.</xsl:text>
+          </xsl:with-param>
+        </xsl:call-template>
+        </xsl:if>
+        <xsl:apply-templates select="titleabbrev" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="titleabbrev">
+        <xsl:apply-templates select="titleabbrev" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="following-sibling::titleabbrev">
+        <xsl:apply-templates select="following-sibling::titleabbrev" mode="copy"/>
+      </xsl:when>
+    </xsl:choose>
+
+    <xsl:choose>
+      <xsl:when test="subtitle and following-sibling::subtitle">
+        <xsl:if test="subtitle != following-sibling::subtitle">
+          <xsl:call-template name="emit-message">
+            <xsl:with-param name="message">
+              <xsl:text>Check </xsl:text>
+              <xsl:value-of select="name(..)"/>
+              <xsl:text> subtitle.</xsl:text>
+            </xsl:with-param>
+          </xsl:call-template>
+        </xsl:if>
+        <xsl:apply-templates select="subtitle" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="subtitle">
+        <xsl:apply-templates select="subtitle" mode="copy"/>
+      </xsl:when>
+      <xsl:when test="following-sibling::subtitle">
+        <xsl:apply-templates select="following-sibling::subtitle" mode="copy"/>
+      </xsl:when>
+    </xsl:choose>
+
+    <xsl:apply-templates/>
+  </info>
+</xsl:template>
+
+<xsl:template match="refentryinfo"
+              priority="200">
+  <info>
+    <xsl:call-template name="copy.attributes"/>
+
+    <!-- titles can be inside or outside or both. fix that -->
+    <xsl:if test="title">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Discarding title from refentryinfo!</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+    </xsl:if>
+
+    <xsl:if test="titleabbrev">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Discarding titleabbrev from refentryinfo!</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+    </xsl:if>
+
+    <xsl:if test="subtitle">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Discarding subtitle from refentryinfo!</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+    </xsl:if>
+
+    <xsl:apply-templates/>
+  </info>
+</xsl:template>
+
+<xsl:template match="refmiscinfo"
+              priority="200">
+  <refmiscinfo>
+    <xsl:call-template name="copy.attributes">
+      <xsl:with-param name="suppress" select="'class'"/>
+    </xsl:call-template>
+    <xsl:if test="@class">
+      <xsl:choose>
+       <xsl:when test="@class = 'source'
+                       or @class = 'version'
+                       or @class = 'manual'
+                       or @class = 'sectdesc'
+                       or @class = 'software'">
+         <xsl:attribute name="class">
+           <xsl:value-of select="@class"/>
+         </xsl:attribute>
+       </xsl:when>
+       <xsl:otherwise>
+         <xsl:attribute name="class">
+           <xsl:value-of select="'other'"/>
+         </xsl:attribute>
+         <xsl:attribute name="otherclass">
+           <xsl:value-of select="@class"/>
+         </xsl:attribute>
+       </xsl:otherwise>
+      </xsl:choose>
+    </xsl:if>
+    <xsl:apply-templates/>
+  </refmiscinfo>
+</xsl:template>
+
+<xsl:template match="corpauthor" priority="200">
+  <author>
+    <xsl:call-template name="copy.attributes"/>
+    <orgname>
+      <xsl:apply-templates/>
+    </orgname>
+  </author>
+</xsl:template>
+
+<xsl:template match="corpname" priority="200">
+  <orgname>
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </orgname>
+</xsl:template>
+
+<xsl:template match="author[not(personname)]|editor[not(personname)]|othercredit[not(personname)]" priority="200">
+  <xsl:copy>
+    <xsl:call-template name="copy.attributes"/>
+    <personname>
+      <xsl:apply-templates select="honorific|firstname|surname|othername|lineage"/>
+    </personname>
+    <xsl:apply-templates select="*[not(self::honorific|self::firstname|self::surname
+                                   |self::othername|self::lineage)]"/>
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template match="address|programlisting|screen|funcsynopsisinfo
+                     |classsynopsisinfo|literallayout" priority="200">
+  <xsl:copy>
+    <xsl:call-template name="copy.attributes">
+      <xsl:with-param name="suppress" select="'format'"/>
+    </xsl:call-template>
+    <xsl:apply-templates/>
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template match="productname[@class]" priority="200">
+  <xsl:call-template name="emit-message">
+    <xsl:with-param name="message">
+      <xsl:text>Dropping class attribute from productname</xsl:text>
+    </xsl:with-param>
+  </xsl:call-template>
+  <xsl:copy>
+    <xsl:call-template name="copy.attributes">
+      <xsl:with-param name="suppress" select="'class'"/>
+    </xsl:call-template>
+    <xsl:apply-templates/>
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template match="dedication|preface|chapter|appendix|part|partintro
+                     |article|bibliography|glossary|glossdiv|index
+                    |reference[not(referenceinfo)]
+                     |book" priority="200">
+  <xsl:choose>
+    <xsl:when test="not(dedicationinfo|prefaceinfo|chapterinfo
+                       |appendixinfo|partinfo
+                        |articleinfo|artheader|bibliographyinfo
+                       |glossaryinfo|indexinfo
+                        |bookinfo)">
+      <xsl:copy>
+        <xsl:call-template name="copy.attributes"/>
+        <xsl:if test="title|subtitle|titleabbrev">
+          <info>
+            <xsl:apply-templates select="title" mode="copy"/>
+            <xsl:apply-templates select="titleabbrev" mode="copy"/>
+            <xsl:apply-templates select="subtitle" mode="copy"/>
+            <xsl:apply-templates select="abstract" mode="copy"/>
+          </info>
+        </xsl:if>
+        <xsl:apply-templates/>
+      </xsl:copy>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:copy>
+        <xsl:call-template name="copy.attributes"/>
+        <xsl:apply-templates/>
+      </xsl:copy>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="formalpara|figure|table[tgroup]|example|blockquote
+                     |caution|important|note|warning|tip
+                     |bibliodiv|glossarydiv|indexdiv
+                    |orderedlist|itemizedlist|variablelist|procedure
+                    |task|tasksummary|taskprerequisites|taskrelated
+                    |sidebar"
+             priority="200">
+  <xsl:choose>
+    <xsl:when test="blockinfo">
+      <xsl:copy>
+        <xsl:call-template name="copy.attributes"/>
+        <xsl:apply-templates/>
+      </xsl:copy>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:copy>
+        <xsl:call-template name="copy.attributes"/>
+
+       <xsl:if test="title|titleabbrev|subtitle">
+         <info>
+           <xsl:apply-templates select="title" mode="copy"/>
+           <xsl:apply-templates select="titleabbrev" mode="copy"/>
+           <xsl:apply-templates select="subtitle" mode="copy"/>
+         </info>
+       </xsl:if>
+
+        <xsl:apply-templates/>
+      </xsl:copy>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="equation" priority="200">
+  <xsl:choose>
+    <xsl:when test="not(title)">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param
+            name="message"
+            >Convert equation without title to informal equation.</xsl:with-param>
+      </xsl:call-template>
+      <informalequation>
+        <xsl:call-template name="copy.attributes"/>
+        <xsl:apply-templates/>
+      </informalequation>
+    </xsl:when>
+    <xsl:when test="blockinfo">
+      <xsl:copy>
+        <xsl:call-template name="copy.attributes"/>
+        <xsl:apply-templates/>
+      </xsl:copy>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:copy>
+        <xsl:call-template name="copy.attributes"/>
+        <info>
+          <xsl:apply-templates select="title" mode="copy"/>
+          <xsl:apply-templates select="titleabbrev" mode="copy"/>
+          <xsl:apply-templates select="subtitle" mode="copy"/>
+        </info>
+        <xsl:apply-templates/>
+      </xsl:copy>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="sect1|sect2|sect3|sect4|sect5|section"
+             priority="200">
+  <section>
+    <xsl:call-template name="copy.attributes"/>
+
+    <xsl:if test="not(sect1info|sect2info|sect3info|sect4info|sect5info|sectioninfo)">
+      <info>
+        <xsl:apply-templates select="title" mode="copy"/>
+        <xsl:apply-templates select="titleabbrev" mode="copy"/>
+        <xsl:apply-templates select="subtitle" mode="copy"/>
+        <xsl:apply-templates select="abstract" mode="copy"/>
+      </info>
+    </xsl:if>
+    <xsl:apply-templates/>
+  </section>
+</xsl:template>
+
+<xsl:template match="simplesect"
+             priority="200">
+  <simplesect>
+    <xsl:call-template name="copy.attributes"/>
+    <info>
+      <xsl:apply-templates select="title" mode="copy"/>
+      <xsl:apply-templates select="titleabbrev" mode="copy"/>
+      <xsl:apply-templates select="subtitle" mode="copy"/>
+      <xsl:apply-templates select="abstract" mode="copy"/>
+    </info>
+    <xsl:apply-templates/>
+  </simplesect>
+</xsl:template>
+
+<xsl:template match="refsect1|refsect2|refsect3|refsection" priority="200">
+  <refsection>
+    <xsl:call-template name="copy.attributes"/>
+
+    <xsl:if test="not(refsect1info|refsect2info|refsect3info|refsectioninfo)">
+      <info>
+        <xsl:apply-templates select="title" mode="copy"/>
+        <xsl:apply-templates select="titleabbrev" mode="copy"/>
+        <xsl:apply-templates select="subtitle" mode="copy"/>
+        <xsl:apply-templates select="abstract" mode="copy"/>
+      </info>
+    </xsl:if>
+    <xsl:apply-templates/>
+  </refsection>
+</xsl:template>
+
+<xsl:template match="imagedata|videodata|audiodata|textdata" priority="200">
+  <xsl:copy>
+    <xsl:call-template name="copy.attributes">
+      <xsl:with-param name="suppress" select="'srccredit'"/>
+    </xsl:call-template>
+    <xsl:if test="@srccredit">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Check conversion of srccredit </xsl:text>
+          <xsl:text>(othercredit="srccredit").</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+      <info>
+        <othercredit class="other" otherclass="srccredit">
+          <orgname>???</orgname>
+          <contrib>
+            <xsl:value-of select="@srccredit"/>
+          </contrib>
+        </othercredit>
+      </info>
+    </xsl:if>
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template match="sgmltag" priority="200">
+  <tag>
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:if test="@class = 'sgmlcomment'">
+      <xsl:attribute name="class">comment</xsl:attribute>
+    </xsl:if>
+    <xsl:apply-templates/>
+  </tag>
+</xsl:template>
+
+<xsl:template match="inlinegraphic[@format='linespecific']" priority="210">
+  <textobject>
+    <textdata>
+      <xsl:call-template name="copy.attributes"/>
+    </textdata>
+  </textobject>
+</xsl:template>
+
+<xsl:template match="inlinegraphic" priority="200">
+  <inlinemediaobject>
+    <imageobject>
+      <imagedata>
+       <xsl:call-template name="copy.attributes"/>
+      </imagedata>
+    </imageobject>
+  </inlinemediaobject>
+</xsl:template>
+
+<xsl:template match="graphic[@format='linespecific']" priority="210">
+  <mediaobject>
+    <textobject>
+      <textdata>
+       <xsl:call-template name="copy.attributes"/>
+      </textdata>
+    </textobject>
+  </mediaobject>
+</xsl:template>
+
+<xsl:template match="graphic" priority="200">
+  <mediaobject>
+    <imageobject>
+      <imagedata>
+       <xsl:call-template name="copy.attributes"/>
+      </imagedata>
+    </imageobject>
+  </mediaobject>
+</xsl:template>
+
+<xsl:template match="pubsnumber" priority="200">
+  <biblioid class="pubsnumber">
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </biblioid>
+</xsl:template>
+
+<xsl:template match="invpartnumber" priority="200">
+  <xsl:call-template name="emit-message">
+    <xsl:with-param name="message">
+      <xsl:text>Converting invpartnumber to biblioid otherclass="invpartnumber".</xsl:text>
+    </xsl:with-param>
+  </xsl:call-template>
+  <biblioid class="other" otherclass="invpartnumber">
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </biblioid>
+</xsl:template>
+
+<xsl:template match="contractsponsor" priority="200">
+  <xsl:variable name="contractnum"
+                select="preceding-sibling::contractnum|following-sibling::contractnum"/>
+
+  <xsl:call-template name="emit-message">
+    <xsl:with-param name="message">
+      <xsl:text>Converting contractsponsor to othercredit="contractsponsor".</xsl:text>
+    </xsl:with-param>
+  </xsl:call-template>
+
+  <othercredit class="other" otherclass="contractsponsor">
+    <orgname>
+      <xsl:call-template name="copy.attributes"/>
+      <xsl:apply-templates/>
+    </orgname>
+    <xsl:for-each select="$contractnum">
+      <contrib role="contractnum">
+        <xsl:apply-templates select="node()"/>
+      </contrib>
+    </xsl:for-each>
+  </othercredit>
+</xsl:template>
+
+<xsl:template match="contractnum" priority="200">
+  <xsl:if test="not(preceding-sibling::contractsponsor
+                    |following-sibling::contractsponsor)
+                and not(preceding-sibling::contractnum)">
+    <xsl:call-template name="emit-message">
+      <xsl:with-param name="message">
+        <xsl:text>Converting contractnum to othercredit="contractnum".</xsl:text>
+      </xsl:with-param>
+    </xsl:call-template>
+
+    <othercredit class="other" otherclass="contractnum">
+      <orgname>???</orgname>
+      <xsl:for-each select="self::contractnum
+                            |preceding-sibling::contractnum
+                            |following-sibling::contractnum">
+        <contrib>
+          <xsl:apply-templates select="node()"/>
+        </contrib>
+      </xsl:for-each>
+    </othercredit>
+  </xsl:if>
+</xsl:template>
+
+<xsl:template match="isbn|issn" priority="200">
+  <biblioid class="{local-name(.)}">
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </biblioid>
+</xsl:template>
+
+<xsl:template match="biblioid[count(*) = 1
+                             and ulink
+                             and normalize-space(text()) = '']" priority="200">
+  <biblioid xlink:href="{ulink/@url}">
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates select="ulink/node()"/>
+  </biblioid>
+</xsl:template>
+
+<xsl:template match="authorblurb" priority="200">
+  <personblurb>
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </personblurb>
+</xsl:template>
+
+<xsl:template match="collabname" priority="200">
+  <xsl:call-template name="emit-message">
+    <xsl:with-param name="message">
+      <xsl:text>Check conversion of collabname </xsl:text>
+      <xsl:text>(orgname role="collabname").</xsl:text>
+    </xsl:with-param>
+  </xsl:call-template>
+  <orgname role="collabname">
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </orgname>
+</xsl:template>
+
+<xsl:template match="modespec" priority="200">
+  <xsl:call-template name="emit-message">
+    <xsl:with-param name="message">
+      <xsl:text>Discarding modespec (</xsl:text>
+      <xsl:value-of select="."/>
+      <xsl:text>).</xsl:text>
+    </xsl:with-param>
+  </xsl:call-template>
+</xsl:template>
+
+<xsl:template match="mediaobjectco" priority="200">
+  <mediaobject>
+    <xsl:copy-of select="@*"/>
+    <xsl:apply-templates/>
+  </mediaobject>
+</xsl:template>
+
+<xsl:template match="remark" priority="200">
+  <!-- get rid of any embedded markup -->
+  <remark>
+    <xsl:copy-of select="@*"/>
+    <xsl:value-of select="."/>
+  </remark>
+</xsl:template>
+
+<xsl:template match="biblioentry/title
+                     |bibliomset/title
+                     |biblioset/title
+                     |bibliomixed/title" priority="400">
+  <citetitle>
+    <xsl:copy-of select="@*"/>
+    <xsl:apply-templates/>
+  </citetitle>
+</xsl:template>
+
+<xsl:template match="biblioentry/titleabbrev|biblioentry/subtitle
+                     |bibliomset/titleabbrev|bibliomset/subtitle
+                     |biblioset/titleabbrev|biblioset/subtitle
+                     |bibliomixed/titleabbrev|bibliomixed/subtitle"
+             priority="400">
+  <xsl:copy>
+    <xsl:copy-of select="@*"/>
+    <xsl:apply-templates/>
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template match="biblioentry/contrib
+                     |bibliomset/contrib
+                     |bibliomixed/contrib" priority="200">
+  <xsl:call-template name="emit-message">
+    <xsl:with-param name="message">
+      <xsl:text>Check conversion of contrib </xsl:text>
+      <xsl:text>(othercontrib="contrib").</xsl:text>
+    </xsl:with-param>
+  </xsl:call-template>
+  <othercredit class="other" otherclass="contrib">
+    <orgname>???</orgname>
+    <contrib>
+      <xsl:call-template name="copy.attributes"/>
+      <xsl:apply-templates/>
+    </contrib>
+  </othercredit>
+</xsl:template>
+
+<xsl:template match="link" priority="200">
+  <xsl:copy>
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template match="ulink" priority="200">
+  <xsl:choose>
+    <xsl:when test="node()">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Converting ulink to link.</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+
+      <link xlink:href="{@url}">
+       <xsl:call-template name="copy.attributes">
+         <xsl:with-param name="suppress" select="'url'"/>
+       </xsl:call-template>
+       <xsl:apply-templates/>
+      </link>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Converting ulink to uri.</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+
+      <uri xlink:href="{@url}">
+       <xsl:call-template name="copy.attributes">
+         <xsl:with-param name="suppress" select="'url'"/>
+       </xsl:call-template>
+       <xsl:value-of select="@url"/>
+      </uri>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="olink" priority="200">
+  <xsl:if test="@linkmode">
+    <xsl:call-template name="emit-message">
+      <xsl:with-param name="message">
+        <xsl:text>Discarding linkmode on olink.</xsl:text>
+      </xsl:with-param>
+    </xsl:call-template>
+  </xsl:if>
+
+  <xsl:choose>
+    <xsl:when test="@targetdocent">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Converting olink targetdocent to targetdoc.</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+
+      <olink targetdoc="{unparsed-entity-uri(@targetdocent)}">
+       <xsl:for-each select="@*">
+         <xsl:if test="name(.) != 'targetdocent'
+                       and name(.) != 'linkmode'">
+           <xsl:copy/>
+         </xsl:if>
+       </xsl:for-each>
+       <xsl:apply-templates/>
+      </olink>
+    </xsl:when>
+    <xsl:otherwise>
+      <olink>
+       <xsl:for-each select="@*">
+         <xsl:if test="name(.) != 'linkmode'">
+           <xsl:copy/>
+         </xsl:if>
+       </xsl:for-each>
+       <xsl:apply-templates/>
+      </olink>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="biblioentry/firstname
+                     |biblioentry/surname
+                     |biblioentry/othername
+                     |biblioentry/lineage
+                     |biblioentry/honorific
+                     |bibliomset/firstname
+                     |bibliomset/surname
+                     |bibliomset/othername
+                     |bibliomset/lineage
+                     |bibliomset/honorific" priority="200">
+  <xsl:choose>
+    <xsl:when test="preceding-sibling::firstname
+                    |preceding-sibling::surname
+                    |preceding-sibling::othername
+                    |preceding-sibling::lineage
+                    |preceding-sibling::honorific">
+      <!-- nop -->
+    </xsl:when>
+    <xsl:otherwise>
+      <personname>
+        <xsl:apply-templates select="../firstname
+                                     |../surname
+                                     |../othername
+                                     |../lineage
+                                     |../honorific" mode="copy"/>
+      </personname>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="areaset" priority="200">
+  <xsl:copy>
+    <xsl:call-template name="copy.attributes">
+      <xsl:with-param name="suppress" select="'coords'"/>
+    </xsl:call-template>
+    <xsl:apply-templates/>
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template match="date|pubdate" priority="200">
+  <xsl:variable name="rp1" select="substring-before(normalize-space(.), ' ')"/>
+  <xsl:variable name="rp2"
+               select="substring-before(substring-after(normalize-space(.), ' '),
+                                        ' ')"/>
+  <xsl:variable name="rp3"
+               select="substring-after(substring-after(normalize-space(.), ' '), ' ')"/>
+
+  <xsl:variable name="p1">
+    <xsl:choose>
+      <xsl:when test="contains($rp1, ',')">
+       <xsl:value-of select="substring-before($rp1, ',')"/>
+      </xsl:when>
+      <xsl:otherwise>
+       <xsl:value-of select="$rp1"/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:variable>
+
+  <xsl:variable name="p2">
+    <xsl:choose>
+      <xsl:when test="contains($rp2, ',')">
+       <xsl:value-of select="substring-before($rp2, ',')"/>
+      </xsl:when>
+      <xsl:otherwise>
+       <xsl:value-of select="$rp2"/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:variable>
+
+  <xsl:variable name="p3">
+    <xsl:choose>
+      <xsl:when test="contains($rp3, ',')">
+       <xsl:value-of select="substring-before($rp3, ',')"/>
+      </xsl:when>
+      <xsl:otherwise>
+       <xsl:value-of select="$rp3"/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:variable>
+
+  <xsl:variable name="date">
+    <xsl:choose>
+      <xsl:when test="string($p1+1) != 'NaN' and string($p3+1) != 'NaN'">
+       <xsl:choose>
+         <xsl:when test="$p2 = 'Jan' or $p2 = 'January'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-01-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Feb' or $p2 = 'February'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-02-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Mar' or $p2 = 'March'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-03-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Apr' or $p2 = 'April'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-04-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'May'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-05-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Jun' or $p2 = 'June'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-06-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Jul' or $p2 = 'July'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-07-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Aug' or $p2 = 'August'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-08-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Sep' or $p2 = 'September'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-09-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Oct' or $p2 = 'October'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-10-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Nov' or $p2 = 'November'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-11-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p2 = 'Dec' or $p2 = 'December'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-12-</xsl:text>
+           <xsl:number value="$p1" format="01"/>
+         </xsl:when>
+         <xsl:otherwise>
+           <xsl:apply-templates/>
+         </xsl:otherwise>
+       </xsl:choose>
+      </xsl:when>
+      <xsl:when test="string($p2+1) != 'NaN' and string($p3+1) != 'NaN'">
+       <xsl:choose>
+         <xsl:when test="$p1 = 'Jan' or $p1 = 'January'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-01-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Feb' or $p1 = 'February'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-02-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Mar' or $p1 = 'March'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-03-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Apr' or $p1 = 'April'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-04-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'May'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-05-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Jun' or $p1 = 'June'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-06-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Jul' or $p1 = 'July'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-07-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Aug' or $p1 = 'August'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-08-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Sep' or $p1 = 'September'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-09-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Oct' or $p1 = 'October'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-10-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Nov' or $p1 = 'November'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-11-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:when test="$p1 = 'Dec' or $p1 = 'December'">
+           <xsl:number value="$p3" format="0001"/>
+           <xsl:text>-12-</xsl:text>
+           <xsl:number value="$p2" format="01"/>
+         </xsl:when>
+         <xsl:otherwise>
+           <xsl:apply-templates/>
+         </xsl:otherwise>
+       </xsl:choose>
+      </xsl:when>
+      <xsl:otherwise>
+       <xsl:apply-templates/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:variable>
+
+  <xsl:choose>
+    <xsl:when test="normalize-space($date) != normalize-space(.)">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Converted </xsl:text>
+          <xsl:value-of select="normalize-space(.)"/>
+          <xsl:text> into </xsl:text>
+          <xsl:value-of select="$date"/>
+          <xsl:text> for </xsl:text>
+          <xsl:value-of select="name(.)"/>
+        </xsl:with-param>
+      </xsl:call-template>
+
+      <xsl:copy>
+       <xsl:copy-of select="@*"/>
+       <xsl:value-of select="$date"/>
+      </xsl:copy>
+    </xsl:when>
+
+    <xsl:when test="$defaultDate != ''">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Unparseable date: </xsl:text>
+          <xsl:value-of select="normalize-space(.)"/>
+          <xsl:text> in </xsl:text>
+          <xsl:value-of select="name(.)"/>
+          <xsl:text> (Using default: </xsl:text>
+          <xsl:value-of select="$defaultDate"/>
+          <xsl:text>)</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+
+      <xsl:copy>
+       <xsl:copy-of select="@*"/>
+       <xsl:copy-of select="$defaultDate"/>
+       <xsl:comment>
+         <xsl:value-of select="."/>
+       </xsl:comment>
+      </xsl:copy>
+    </xsl:when>
+
+    <xsl:otherwise>
+      <!-- these don't really matter anymore
+           <xsl:call-template name="emit-message">
+           <xsl:with-param name="message">
+           <xsl:text>Unparseable date: </xsl:text>
+           <xsl:value-of select="normalize-space(.)"/>
+           <xsl:text> in </xsl:text>
+           <xsl:value-of select="name(.)"/>
+           </xsl:with-param>
+           </xsl:call-template>
+      -->
+      <xsl:copy>
+       <xsl:copy-of select="@*"/>
+       <xsl:apply-templates/>
+      </xsl:copy>
+    </xsl:otherwise>
+  </xsl:choose>      
+</xsl:template>
+
+<xsl:template match="title|subtitle|titleabbrev" priority="300">
+  <!-- nop -->
+</xsl:template>
+
+<xsl:template match="abstract" priority="300">
+  <xsl:if test="not(contains(name(parent::*),'info'))">
+    <xsl:call-template name="emit-message">
+      <xsl:with-param name="message">
+       <xsl:text>Check abstract; moved into info correctly?</xsl:text>
+      </xsl:with-param>
+    </xsl:call-template>
+  </xsl:if>
+</xsl:template>
+
+<xsl:template match="indexterm">
+  <!-- don't copy the defaulted significance='normal' attribute -->
+  <indexterm>
+    <xsl:call-template name="copy.attributes">
+      <xsl:with-param name="suppress">
+       <xsl:if test="@significance = 'normal'">significance</xsl:if>
+      </xsl:with-param>
+    </xsl:call-template>
+    <xsl:apply-templates/>
+  </indexterm>
+</xsl:template>
+
+<xsl:template match="ackno" priority="200">
+  <acknowledgements>
+    <xsl:copy-of select="@*"/>
+    <para>
+      <xsl:apply-templates/>
+    </para>
+  </acknowledgements>
+</xsl:template>
+
+<xsl:template match="lot|lotentry|tocback|tocchap|tocfront|toclevel1|
+                    toclevel2|toclevel3|toclevel4|toclevel5|tocpart" priority="200">
+  <tocdiv>
+    <xsl:copy-of select="@*"/>
+    <xsl:apply-templates/>
+  </tocdiv>
+</xsl:template>
+
+<xsl:template match="action" priority="200">
+  <phrase remap="action">
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </phrase>
+</xsl:template>
+
+<xsl:template match="beginpage" priority="200">
+  <xsl:comment> beginpage pagenum=<xsl:value-of select="@pagenum"/> </xsl:comment>
+  <xsl:call-template name="emit-message">
+    <xsl:with-param name="message">
+      <xsl:text>Replacing beginpage with comment</xsl:text>
+    </xsl:with-param>
+  </xsl:call-template>
+</xsl:template>
+
+<xsl:template match="structname|structfield" priority="200">
+  <varname remap="{local-name(.)}">
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </varname>
+</xsl:template>
+
+<!-- ====================================================================== -->
+
+<!-- 6 Feb 2008, ndw changed mode=copy so that it only copies the first level,
+     then it switches back to "normal" mode so that other rewriting templates
+     catch embedded fixes -->
+
+<!--
+<xsl:template match="ulink" priority="200" mode="copy">
+  <xsl:choose>
+    <xsl:when test="node()">
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Converting ulink to phrase.</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+
+      <phrase xlink:href="{@url}">
+       <xsl:call-template name="copy.attributes">
+         <xsl:with-param name="suppress" select="'url'"/>
+       </xsl:call-template>
+       <xsl:apply-templates/>
+      </phrase>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:call-template name="emit-message">
+        <xsl:with-param name="message">
+          <xsl:text>Converting ulink to uri.</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+
+      <uri xlink:href="{@url}">
+       <xsl:call-template name="copy.attributes">
+         <xsl:with-param name="suppress" select="'url'"/>
+       </xsl:call-template>
+       <xsl:value-of select="@url"/>
+      </uri>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="sgmltag" priority="200" mode="copy">
+  <tag>
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </tag>
+</xsl:template>
+-->
+
+<xsl:template match="*" mode="copy">
+  <xsl:copy>
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </xsl:copy>
+</xsl:template>
+
+<!--
+<xsl:template match="comment()|processing-instruction()|text()" mode="copy">
+  <xsl:copy/>
+</xsl:template>
+-->
+
+<!-- ====================================================================== -->
+
+<xsl:template match="*">
+  <xsl:copy>
+    <xsl:call-template name="copy.attributes"/>
+    <xsl:apply-templates/>
+  </xsl:copy>
+</xsl:template>
+
+<xsl:template match="comment()|processing-instruction()|text()">
+  <xsl:copy/>
+</xsl:template>
+
+<!-- ====================================================================== -->
+
+<xsl:template name="copy.attributes">
+  <xsl:param name="src" select="."/>
+  <xsl:param name="suppress" select="''"/>
+
+  <xsl:for-each select="$src/@*">
+    <xsl:choose>
+      <xsl:when test="local-name(.) = 'moreinfo'">
+        <xsl:call-template name="emit-message">
+          <xsl:with-param name="message">
+            <xsl:text>Discarding moreinfo on </xsl:text>
+            <xsl:value-of select="local-name($src)"/>
+          </xsl:with-param>
+        </xsl:call-template>
+      </xsl:when>
+      <xsl:when test="local-name(.) = 'lang'">
+        <xsl:attribute name="xml:lang">
+          <xsl:value-of select="."/>
+        </xsl:attribute>
+      </xsl:when>
+      <xsl:when test="local-name(.) = 'id'">
+        <xsl:attribute name="xml:id">
+          <xsl:value-of select="."/>
+        </xsl:attribute>
+      </xsl:when>
+      <xsl:when test="$suppress = local-name(.)"/>
+      <xsl:when test="local-name(.) = 'float'">
+       <xsl:choose>
+         <xsl:when test=". = '1'">
+            <xsl:call-template name="emit-message">
+              <xsl:with-param name="message">
+                <xsl:text>Discarding float on </xsl:text>
+                <xsl:value-of select="local-name($src)"/>
+              </xsl:with-param>
+            </xsl:call-template>
+            <xsl:if test="not($src/@floatstyle)">
+             <xsl:call-template name="emit-message">
+                <xsl:with-param name="message">
+                  <xsl:text>Adding floatstyle='normal' on </xsl:text>
+                  <xsl:value-of select="local-name($src)"/>
+                </xsl:with-param>
+              </xsl:call-template>
+              <xsl:attribute name="floatstyle">
+                <xsl:text>normal</xsl:text>
+             </xsl:attribute>
+           </xsl:if>
+         </xsl:when>
+         <xsl:when test=". = '0'">
+           <xsl:call-template name="emit-message">
+              <xsl:with-param name="message">
+                <xsl:text>Discarding float on </xsl:text>
+                <xsl:value-of select="local-name($src)"/>
+              </xsl:with-param>
+            </xsl:call-template>
+          </xsl:when>
+         <xsl:otherwise>
+           <xsl:call-template name="emit-message">
+          <xsl:with-param name="message">
+            <xsl:text>Discarding float on </xsl:text>
+            <xsl:value-of select="local-name($src)"/>
+          </xsl:with-param>
+            </xsl:call-template>
+            <xsl:if test="not($src/@floatstyle)">
+              <xsl:call-template name="emit-message">
+                <xsl:with-param name="message">
+                  <xsl:text>Adding floatstyle='</xsl:text>
+                  <xsl:value-of select="."/>
+                  <xsl:text>' on </xsl:text>
+                  <xsl:value-of select="local-name($src)"/>
+                </xsl:with-param>
+              </xsl:call-template>
+              <xsl:attribute name="floatstyle">
+               <xsl:value-of select="."/>
+             </xsl:attribute>
+           </xsl:if>
+         </xsl:otherwise>
+       </xsl:choose>
+      </xsl:when>
+      <xsl:when test="local-name(.) = 'entityref'">
+       <xsl:attribute name="fileref">
+         <xsl:value-of select="unparsed-entity-uri(@entityref)"/>
+       </xsl:attribute>
+      </xsl:when>
+
+      <xsl:when test="local-name($src) = 'simplemsgentry'
+                     and local-name(.) = 'audience'">
+        <xsl:attribute name="msgaud">
+          <xsl:value-of select="."/>
+        </xsl:attribute>
+      </xsl:when>
+      <xsl:when test="local-name($src) = 'simplemsgentry'
+                     and local-name(.) = 'origin'">
+        <xsl:attribute name="msgorig">
+          <xsl:value-of select="."/>
+        </xsl:attribute>
+      </xsl:when>
+      <xsl:when test="local-name($src) = 'simplemsgentry'
+                     and local-name(.) = 'level'">
+        <xsl:attribute name="msglevel">
+          <xsl:value-of select="."/>
+        </xsl:attribute>
+      </xsl:when>
+
+      <!-- * for upgrading XSL litprog params documentation -->
+      <xsl:when test="local-name($src) = 'refmiscinfo'
+                      and local-name(.) = 'role'
+                      and . = 'type'
+                      ">
+        <xsl:call-template name="emit-message">
+          <xsl:with-param name="message">
+            <xsl:text>Converting refmiscinfo@role=type to </xsl:text>
+            <xsl:text>@class=other,otherclass=type</xsl:text>
+          </xsl:with-param>
+        </xsl:call-template>
+        <xsl:attribute name="class">other</xsl:attribute>
+        <xsl:attribute name="otherclass">type</xsl:attribute>
+      </xsl:when>
+
+      <xsl:otherwise>
+        <xsl:copy/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:for-each>
+</xsl:template>
+
+<!-- ====================================================================== -->
+
+<xsl:template match="*" mode="addNS">
+  <xsl:choose>
+    <xsl:when test="namespace-uri(.) = ''">
+      <xsl:element name="{local-name(.)}"
+                  namespace="http://docbook.org/ns/docbook">
+       <xsl:if test="not(parent::*)">
+         <xsl:attribute name="version">5.0</xsl:attribute>
+       </xsl:if>
+       <xsl:copy-of select="@*"/>
+       <xsl:apply-templates mode="addNS"/>
+      </xsl:element>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:copy>
+       <xsl:if test="not(parent::*)">
+         <xsl:attribute name="version">5.0</xsl:attribute>
+       </xsl:if>
+       <xsl:copy-of select="@*"/>
+       <xsl:apply-templates mode="addNS"/>
+      </xsl:copy>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="comment()|processing-instruction()|text()" mode="addNS">
+  <xsl:copy/>
+</xsl:template>
+
+<!-- ====================================================================== -->
+
+<xsl:template name="emit-message">
+  <xsl:param name="message"/>
+  <xsl:message>
+    <xsl:value-of select="$message"/>
+    <xsl:text> (</xsl:text>
+    <xsl:value-of select="$rootid"/>
+    <xsl:text>)</xsl:text>
+  </xsl:message>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/odk/OdkJcrUtils.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/odk/OdkJcrUtils.java
new file mode 100644 (file)
index 0000000..881523f
--- /dev/null
@@ -0,0 +1,196 @@
+package org.argeo.app.jcr.odk;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.app.api.EntityMimeType;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.odk.OdkNames;
+import org.argeo.app.odk.OrxListName;
+import org.argeo.app.odk.OrxManifestName;
+import org.argeo.cms.util.DigestUtils;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.jcr.JcrxApi;
+
+/** Utilities around ODK. */
+public class OdkJcrUtils {
+       private final static CmsLog log = CmsLog.getLog(OdkJcrUtils.class);
+
+       public static Node loadOdkForm(Node formBase, String name, InputStream in, InputStream... additionalNodes)
+                       throws RepositoryException, IOException {
+               if (!formBase.isNodeType(EntityType.formSet.get()))
+                       throw new IllegalArgumentException(
+                                       "Parent path " + formBase + " must be of type " + EntityType.formSet.get());
+               Node form = JcrUtils.getOrAdd(formBase, name, OrxListName.xform.get(), NodeType.MIX_VERSIONABLE);
+
+               String previousCsum = JcrxApi.getChecksum(form, JcrxApi.MD5);
+               String previousFormId = Jcr.get(form, OrxListName.formID.get());
+               String previousFormVersion = Jcr.get(form, OrxListName.version.get());
+
+               Session s = formBase.getSession();
+               s.importXML(form.getPath(), in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+
+               for (InputStream additionalIn : additionalNodes) {
+                       s.importXML(form.getPath(), additionalIn, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+               }
+               s.save();
+
+               // manage instances
+               // NodeIterator instances =
+               // form.getNodes("h:html/h:head/xforms:model/xforms:instance");
+               NodeIterator instances = form.getNode("h:html/h:head/xforms:model").getNodes("xforms:instance");
+               Node primaryInstance = null;
+               while (instances.hasNext()) {
+                       Node instance = instances.nextNode();
+                       if (primaryInstance == null) {
+                               primaryInstance = instance;
+                       } else {// secondary instances
+                               String instanceId = instance.getProperty("id").getString();
+                               URI instanceUri = null;
+                               if (instance.hasProperty("src"))
+                                       try {
+                                               instanceUri = new URI(instance.getProperty("src").getString());
+                                       } catch (URISyntaxException e) {
+                                               throw new IllegalArgumentException("Instance " + instanceId + " has a badly formatted URI", e);
+                                       }
+                               if (instanceUri != null) {
+                                       if ("jr".equals(instanceUri.getScheme())) {
+                                               String uuid;
+                                               String mimeType;
+                                               String encoding = StandardCharsets.UTF_8.name();
+                                               String type = instanceUri.getHost();
+                                               String path = instanceUri.getPath();
+                                               if ("file".equals(type)) {
+                                                       if (!path.endsWith(".xml"))
+                                                               throw new IllegalArgumentException("File uri " + instanceUri + " must end with .xml");
+                                                       // Work around bug in ODK Collect not supporting paths
+                                                       // path = path.substring(0, path.length() - ".xml".length());
+                                                       // Node target = file.getSession().getNode(path);
+                                                       uuid = path.substring(1, path.length() - ".xml".length());
+                                                       mimeType = EntityMimeType.XML.getMimeType();
+                                               } else if ("file-csv".equals(type)) {
+                                                       if (!path.endsWith(".csv"))
+                                                               throw new IllegalArgumentException("File uri " + instanceUri + " must end with .csv");
+                                                       // Work around bug in ODK Collect not supporting paths
+                                                       // path = path.substring(0, path.length() - ".csv".length());
+                                                       // Node target = file.getSession().getNode(path);
+                                                       uuid = path.substring(1, path.length() - ".csv".length());
+                                                       mimeType = EntityMimeType.CSV.getMimeType();
+                                               } else {
+                                                       throw new IllegalArgumentException("Unsupported instance type " + type);
+                                               }
+                                               Node manifest = JcrUtils.getOrAdd(form, OrxManifestName.manifest.name(),
+                                                               OrxManifestName.manifest.get());
+                                               Node file = JcrUtils.getOrAdd(manifest, instanceId);
+                                               file.addMixin(NodeType.MIX_MIMETYPE);
+                                               file.setProperty(Property.JCR_MIMETYPE, mimeType);
+                                               file.setProperty(Property.JCR_ENCODING, encoding);
+                                               Node target = file.getSession().getNodeByIdentifier(uuid);
+
+//                                             if (target.isNodeType(NodeType.NT_QUERY)) {
+//                                                     Query query = target.getSession().getWorkspace().getQueryManager().getQuery(target);
+//                                                     query.setLimit(10);
+//                                                     QueryResult queryResult = query.execute();
+//                                                     RowIterator rit = queryResult.getRows();
+//                                                     while (rit.hasNext()) {
+//                                                             Row row = rit.nextRow();
+//                                                             for (Value value : row.getValues()) {
+//                                                                     System.out.print(value.getString());
+//                                                                     System.out.print(',');
+//                                                             }
+//                                                             System.out.print('\n');
+//                                                     }
+//
+//                                             }
+
+                                               if (target.isNodeType(NodeType.MIX_REFERENCEABLE)) {
+                                                       file.setProperty(Property.JCR_ID, target);
+                                                       if (file.hasProperty(Property.JCR_PATH))
+                                                               file.getProperty(Property.JCR_PATH).remove();
+                                               } else {
+                                                       file.setProperty(Property.JCR_PATH, target.getPath());
+                                                       if (file.hasProperty(Property.JCR_ID))
+                                                               file.getProperty(Property.JCR_ID).remove();
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               if (primaryInstance == null)
+                       throw new IllegalArgumentException("No primary instance found in " + form);
+               if (!primaryInstance.hasNodes())
+                       throw new IllegalArgumentException("No data found in primary instance of " + form);
+               NodeIterator primaryInstanceChildren = primaryInstance.getNodes();
+               Node data = primaryInstanceChildren.nextNode();
+               if (primaryInstanceChildren.hasNext())
+                       throw new IllegalArgumentException("More than one data found in primary instance of " + form);
+               String formId = data.getProperty("id").getString();
+               if (previousFormId != null && !formId.equals(previousFormId))
+                       log.warn("Form id of " + form + " changed from " + previousFormId + " to " + formId);
+               form.setProperty(OrxListName.formID.get(), formId);
+               String formVersion = data.getProperty("version").getString();
+
+               if (previousCsum == null)// save before checksuming
+                       s.save();
+               String newCsum;
+               try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+                       s.exportDocumentView(form.getPath() + "/" + OdkNames.H_HTML, out, true, false);
+                       newCsum = DigestUtils.digest(DigestUtils.MD5, out.toByteArray());
+               }
+               if (previousCsum == null) {
+                       JcrxApi.addChecksum(form, newCsum);
+                       JcrUtils.updateLastModified(form);
+                       form.setProperty(OrxListName.version.get(), formVersion);
+                       s.save();
+                       s.getWorkspace().getVersionManager().checkpoint(form.getPath());
+                       if (log.isDebugEnabled())
+                               log.debug("New form " + form);
+               } else {
+                       if (newCsum.equals(previousCsum)) {
+                               // discard
+                               s.refresh(false);
+                               if (log.isDebugEnabled())
+                                       log.debug("Unmodified form " + form);
+                               return form;
+                       } else {
+                               if (formVersion.equals(previousFormVersion)) {
+                                       s.refresh(false);
+                                       throw new IllegalArgumentException("Form " + form + " has been changed but version " + formVersion
+                                                       + " has not been changed, discarding changes...");
+                               }
+                               form.setProperty(OrxListName.version.get(), formVersion);
+                               JcrxApi.addChecksum(form, newCsum);
+                               JcrUtils.updateLastModified(form);
+                               s.save();
+                               s.getWorkspace().getVersionManager().checkpoint(form.getPath());
+                               if (log.isDebugEnabled()) {
+                                       log.debug("Updated form " + form);
+                                       log.debug("Previous csum " + previousCsum);
+                                       log.debug("New csum " + newCsum);
+                               }
+                       }
+               }
+               return form;
+       }
+
+       /** Singleton. */
+       private OdkJcrUtils() {
+
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTerm.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTerm.java
new file mode 100644 (file)
index 0000000..db4e4a6
--- /dev/null
@@ -0,0 +1,62 @@
+package org.argeo.app.jcr.terms;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.argeo.app.api.Term;
+
+/**
+ * A single term. Helper to optimise {@link SuiteTermsManager} implementation.
+ */
+class SuiteTerm implements Term {
+       private final String name;
+       private final String relativePath;
+       private final SuiteTypology typology;
+       private final String id;
+
+       private final SuiteTerm parentTerm;
+       private final List<SuiteTerm> subTerms = new ArrayList<>();
+
+       SuiteTerm(SuiteTypology typology, String relativePath, SuiteTerm parentTerm) {
+               this.typology = typology;
+               this.parentTerm = parentTerm;
+               this.relativePath = relativePath;
+               int index = relativePath.lastIndexOf('/');
+               if (index > 0) {
+                       this.name = relativePath.substring(index + 1);
+               } else {
+                       this.name = relativePath;
+               }
+               id = typology.getName() + '/' + relativePath;
+       }
+
+       @Override
+       public String getId() {
+               return id;
+       }
+
+       @Override
+       public String getName() {
+               return name;
+       }
+
+       public String getRelativePath() {
+               return relativePath;
+       }
+
+       @Override
+       public SuiteTypology getTypology() {
+               return typology;
+       }
+
+       @Override
+       public List<SuiteTerm> getSubTerms() {
+               return subTerms;
+       }
+
+       @Override
+       public SuiteTerm getParentTerm() {
+               return parentTerm;
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTermsManager.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTermsManager.java
new file mode 100644 (file)
index 0000000..6327a55
--- /dev/null
@@ -0,0 +1,103 @@
+package org.argeo.app.jcr.terms;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.app.api.EntityNames;
+import org.argeo.app.api.EntityType;
+import org.argeo.app.api.Term;
+import org.argeo.app.api.TermsManager;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+
+/** Argeo Suite implementation of terms manager. */
+public class SuiteTermsManager implements TermsManager {
+       private final Map<String, SuiteTerm> terms = new HashMap<>();
+       private final Map<String, SuiteTypology> typologies = new HashMap<>();
+
+       // JCR
+       private Repository repository;
+       private Session adminSession;
+
+       public void init() {
+               adminSession = CmsJcrUtils.openDataAdminSession(repository, CmsConstants.SYS_WORKSPACE);
+       }
+
+       @Override
+       public List<Term> listAllTerms(String typology) {
+               List<Term> res = new ArrayList<>();
+               SuiteTypology t = getTypology(typology);
+               for (SuiteTerm term : t.getAllTerms()) {
+                       res.add(term);
+               }
+               return res;
+       }
+
+       @Override
+       public SuiteTerm getTerm(String termId) {
+               return terms.get(termId);
+       }
+
+       @Override
+       public SuiteTypology getTypology(String typology) {
+               SuiteTypology t = typologies.get(typology);
+               if (t == null) {
+                       Node termsNode = Jcr.getNode(adminSession, "SELECT * FROM [{0}] WHERE NAME()=\"{1}\"",
+                                       EntityType.terms.get(), typology);
+                       if (termsNode == null)
+                               throw new IllegalArgumentException("Typology " + typology + " not found.");
+                       t = loadTypology(termsNode);
+               }
+               return t;
+       }
+
+       SuiteTypology loadTypology(Node termsNode) {
+               try {
+                       SuiteTypology typology = new SuiteTypology(termsNode);
+                       for (Node termNode : Jcr.iterate(termsNode.getNodes())) {
+                               if (termNode.isNodeType(EntityType.term.get())) {
+                                       SuiteTerm term = loadTerm(typology, termNode, null);
+                                       if (!term.getSubTerms().isEmpty())
+                                               typology.markNotFlat();
+                                       typology.getSubTerms().add(term);
+                               }
+                       }
+                       typologies.put(typology.getName(), typology);
+                       return typology;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot load typology from " + termsNode, e);
+               }
+       }
+
+       SuiteTerm loadTerm(SuiteTypology typology, Node termNode, SuiteTerm parentTerm) throws RepositoryException {
+               String name = termNode.getProperty(EntityNames.NAME).getString();
+               String relativePath = parentTerm == null ? name : parentTerm.getRelativePath() + '/' + name;
+               SuiteTerm term = new SuiteTerm(typology, relativePath, parentTerm);
+               terms.put(term.getId(), term);
+               for (Node subTermNode : Jcr.iterate(termNode.getNodes())) {
+                       if (termNode.isNodeType(EntityType.term.get())) {
+                               SuiteTerm subTerm = loadTerm(typology, subTermNode, term);
+                               term.getSubTerms().add(subTerm);
+                       }
+               }
+               return term;
+       }
+
+       public void destroy() {
+               Jcr.logout(adminSession);
+       }
+
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTypology.java b/org.argeo.app.jcr/src/org/argeo/app/jcr/terms/SuiteTypology.java
new file mode 100644 (file)
index 0000000..0040abb
--- /dev/null
@@ -0,0 +1,95 @@
+package org.argeo.app.jcr.terms;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+
+import org.argeo.app.api.Term;
+import org.argeo.app.api.Typology;
+import org.argeo.jcr.Jcr;
+
+/** A typology. Helper to optimise {@link SuiteTermsManager} implementation. */
+class SuiteTypology implements Typology {
+       private final String name;
+       private final Node node;
+       private boolean isFlat = true;
+
+       private final List<SuiteTerm> subTerms = new ArrayList<>();
+
+       public SuiteTypology(Node node) {
+               this.node = node;
+               this.name = Jcr.getName(this.node);
+       }
+
+       @Override
+       public String getId() {
+               return name;
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public Node getNode() {
+               return node;
+       }
+
+       void markNotFlat() {
+               if (isFlat)
+                       isFlat = false;
+       }
+
+       @Override
+       public boolean isFlat() {
+               return isFlat;
+       }
+
+       @Override
+       public List<SuiteTerm> getSubTerms() {
+               return subTerms;
+       }
+
+       public List<SuiteTerm> getAllTerms() {
+               if (isFlat)
+                       return subTerms;
+               else {
+                       List<SuiteTerm> terms = new ArrayList<>();
+                       for (SuiteTerm subTerm : subTerms) {
+                               terms.add(subTerm);
+                               collectSubTerms(terms, subTerm);
+                       }
+                       return terms;
+               }
+       }
+
+       public Term findTermByName(String name) {
+               List<SuiteTerm> collected = new ArrayList<>();
+               for (SuiteTerm subTerm : subTerms) {
+                       collectTermsByName(subTerm, name, collected);
+               }
+               if (collected.isEmpty())
+                       return null;
+               if (collected.size() == 1)
+                       return collected.get(0);
+               throw new IllegalArgumentException(
+                               "There are " + collected.size() + " terms with name " + name + " in typology " + getId());
+       }
+
+       private void collectTermsByName(SuiteTerm term, String name, List<SuiteTerm> collected) {
+               if (term.getName().equals(name)) {
+                       collected.add(term);
+               }
+               for (SuiteTerm subTerm : term.getSubTerms()) {
+                       collectTermsByName(subTerm, name, collected);
+               }
+       }
+
+       private void collectSubTerms(List<SuiteTerm> terms, SuiteTerm term) {
+               for (SuiteTerm subTerm : term.getSubTerms()) {
+                       terms.add(subTerm);
+                       collectSubTerms(terms, subTerm);
+               }
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/internal/app/jcr/AppUserStateImpl.java b/org.argeo.app.jcr/src/org/argeo/internal/app/jcr/AppUserStateImpl.java
new file mode 100644 (file)
index 0000000..faf217a
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.internal.app.jcr;
+
+import javax.jcr.Node;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.app.api.AppUserState;
+import org.argeo.app.jcr.SuiteJcrUtils;
+import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.jcr.acr.JcrContentProvider;
+import org.argeo.jcr.Jcr;
+
+public class AppUserStateImpl implements AppUserState {
+       private JcrContentProvider jcrContentProvider;
+
+       @SuppressWarnings("deprecation")
+       @Override
+       public Content getOrCreateSessionDir(ContentSession contentSession, CmsSession session) {
+               Node userDirNode = jcrContentProvider.doInAdminSession((adminSession) -> {
+                       Node node = SuiteJcrUtils.getOrCreateCmsSessionNode(adminSession, session);
+                       return node;
+               });
+               Content userDir = contentSession
+                               .get(ContentUtils.SLASH + CmsConstants.SYS_WORKSPACE + Jcr.getPath(userDirNode));
+               return userDir;
+       }
+
+       public void setJcrContentProvider(JcrContentProvider jcrContentProvider) {
+               this.jcrContentProvider = jcrContentProvider;
+       }
+
+}
diff --git a/org.argeo.app.jcr/src/org/argeo/internal/app/jcr/SuiteMaintenanceService.java b/org.argeo.app.jcr/src/org/argeo/internal/app/jcr/SuiteMaintenanceService.java
new file mode 100644 (file)
index 0000000..ceeb4f5
--- /dev/null
@@ -0,0 +1,39 @@
+package org.argeo.internal.app.jcr;
+
+import java.io.IOException;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.security.Privilege;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.app.api.EntityType;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.maintenance.AbstractMaintenanceService;
+
+/** Initialises JCR in an Argeo Suite backend. */
+public class SuiteMaintenanceService extends AbstractMaintenanceService {
+       @Override
+       public boolean prepareJcrTree(Session adminSession) throws RepositoryException, IOException {
+               boolean modified = false;
+               Node rootNode = adminSession.getRootNode();
+               if (!rootNode.hasNode(EntityType.user.name())) {
+                       rootNode.addNode(EntityType.user.name(), NodeType.NT_UNSTRUCTURED);
+                       modified = true;
+               }
+               if (modified)
+                       adminSession.save();
+               return modified;
+       }
+
+       @Override
+       public void configurePrivileges(Session adminSession) throws RepositoryException {
+               JcrUtils.addPrivilege(adminSession, EntityType.user.basePath(), CmsConstants.ROLE_USER_ADMIN,
+                               Privilege.JCR_ALL);
+               // JcrUtils.addPrivilege(adminSession, "/", SuiteRole.coworker.dn(),
+               // Privilege.JCR_READ);
+       }
+
+}
index 9392587d917ae5058f85583f3719c906afb2eba7..3740805f1216a7aff661f559f10a7cdd15e8b1d1 100644 (file)
@@ -24,13 +24,14 @@ import javax.servlet.http.Part;
 
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsSession;
-import org.argeo.app.core.SuiteUtils;
 import org.argeo.app.image.ImageProcessor;
+import org.argeo.app.jcr.SuiteJcrUtils;
 import org.argeo.app.odk.OrxType;
 import org.argeo.app.xforms.FormSubmissionListener;
 import org.argeo.cms.auth.RemoteAuthRequest;
 import org.argeo.cms.auth.RemoteAuthUtils;
 import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.jcr.acr.JcrContent;
 import org.argeo.cms.servlet.ServletHttpRequest;
 import org.argeo.jcr.Jcr;
 import org.argeo.jcr.JcrUtils;
@@ -54,9 +55,9 @@ public class OdkSubmissionServlet extends HttpServlet {
                resp.setContentType("text/xml; charset=utf-8");
                resp.setHeader("X-OpenRosa-Version", "1.0");
                resp.setDateHeader("Date", System.currentTimeMillis());
-               
+
                // should be set in HEAD? Let's rather use defaults.
-               //resp.setIntHeader("X-OpenRosa-Accept-Content-Length", 1024 * 1024);
+               // resp.setIntHeader("X-OpenRosa-Accept-Content-Length", 1024 * 1024);
 
                RemoteAuthRequest request = new ServletHttpRequest(req);
                Session session = RemoteAuthUtils.doAs(() -> Jcr.login(repository, null), request);
@@ -67,13 +68,13 @@ public class OdkSubmissionServlet extends HttpServlet {
                try {
                        // TODO centralise at a deeper level
                        adminSession = CmsJcrUtils.openDataAdminSession(repository, null);
-                       SuiteUtils.getOrCreateCmsSessionNode(adminSession, cmsSession);
+                       SuiteJcrUtils.getOrCreateCmsSessionNode(adminSession, cmsSession);
                } finally {
                        Jcr.logout(adminSession);
                }
 
                try {
-                       Node cmsSessionNode = SuiteUtils.getCmsSessionNode(session, cmsSession);
+                       Node cmsSessionNode = SuiteJcrUtils.getCmsSessionNode(session, cmsSession);
                        Node submission = cmsSessionNode.addNode(submissionNameFormatter.format(Instant.now()),
                                        OrxType.submission.get());
                        for (Part part : req.getParts()) {
@@ -117,7 +118,7 @@ public class OdkSubmissionServlet extends HttpServlet {
                        session.save();
                        try {
                                for (FormSubmissionListener submissionListener : submissionListeners) {
-                                       submissionListener.formSubmissionReceived(submission);
+                                       submissionListener.formSubmissionReceived(JcrContent.nodeToContent(submission));
                                }
                        } catch (Exception e) {
                                log.error("Cannot save submision, cancelling...", e);
index 152df6eb5dca15742aa3fc9cdcf41fe37f083472..246a0c20f5d88b9362599a8fc2f6b0b34a51c3a0 100644 (file)
@@ -45,7 +45,7 @@ import org.apache.fop.apps.FopFactory;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.ux.CmsTheme;
 import org.argeo.app.docbook.DbkType;
-import org.argeo.app.docbook.DbkUtils;
+import org.argeo.app.jcr.docbook.DbkJcrUtils;
 import org.argeo.cms.auth.RemoteAuthUtils;
 import org.argeo.cms.servlet.ServletHttpRequest;
 import org.argeo.jcr.Jcr;
@@ -116,7 +116,7 @@ public class DbkServlet extends HttpServlet {
 
                        if (node.hasNode(DbkType.article.get())) {
                                Node dbkNode = node.getNode(DbkType.article.get());
-                               if (DbkUtils.isDbk(dbkNode)) {
+                               if (DbkJcrUtils.isDbk(dbkNode)) {
                                        CmsTheme cmsTheme = null;
                                        String themeId = req.getParameter("themeId");
                                        if (themeId != null) {
diff --git a/org.argeo.product.knowledge/.classpath b/org.argeo.product.knowledge/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.argeo.product.knowledge/.project b/org.argeo.product.knowledge/.project
new file mode 100644 (file)
index 0000000..54f9ad4
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.product.knowledge</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.product.knowledge/OSGI-INF/l10n/bundle.properties b/org.argeo.product.knowledge/OSGI-INF/l10n/bundle.properties
new file mode 100644 (file)
index 0000000..a750a7a
--- /dev/null
@@ -0,0 +1 @@
+appTitle=Argeo Knowledge
diff --git a/org.argeo.product.knowledge/OSGI-INF/swtArgeoApp.xml b/org.argeo.product.knowledge/OSGI-INF/swtArgeoApp.xml
new file mode 100644 (file)
index 0000000..ad18dd9
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.product.knowledge.swtArgeoApp">
+   <implementation class="org.argeo.app.swt.ux.SwtArgeoApp"/>
+   <service>
+      <provide interface="org.argeo.api.cms.CmsApp"/>
+   </service>
+   <properties entry="config/swtArgeoApp.properties"/>
+   <reference bind="addUiProvider" cardinality="0..n" interface="org.argeo.cms.swt.acr.SwtUiProvider" policy="dynamic" unbind="removeUiProvider"/>
+   <reference bind="addTheme" cardinality="1..n" interface="org.argeo.api.cms.ux.CmsTheme" name="CmsTheme" policy="dynamic" unbind="removeTheme"/>
+   <reference bind="addLayer" cardinality="1..n" interface="org.argeo.app.swt.ux.SwtAppLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
+   <reference bind="setCmsContext" cardinality="1..1" interface="org.argeo.api.cms.CmsContext" name="CmsContext" policy="static"/>
+   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.ContentRepository" name="ContentRepository" policy="static"/>
+   <reference bind="setAppUserState" cardinality="1..1" interface="org.argeo.app.api.AppUserState" name="AppUserState" policy="static"/>
+</scr:component>
\ No newline at end of file
diff --git a/org.argeo.product.knowledge/OSGI-INF/termsEntryArea.xml b/org.argeo.product.knowledge/OSGI-INF/termsEntryArea.xml
new file mode 100644 (file)
index 0000000..e241b11
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Terms Entry Area">
+   <implementation class="org.argeo.app.swt.terms.TermsEntryArea"/>
+   <service>
+      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/termsEntryArea.properties"/>
+</scr:component>
diff --git a/org.argeo.product.knowledge/OSGI-INF/termsLayer.xml b/org.argeo.product.knowledge/OSGI-INF/termsLayer.xml
new file mode 100644 (file)
index 0000000..ecfea87
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Terms Layer">
+   <implementation class="org.argeo.app.swt.ux.DefaultEditionLayer"/>
+   <service>
+      <provide interface="org.argeo.app.swt.ux.SwtAppLayer"/>
+   </service>
+   <property name="service.ranking" type="Integer" value="-1000"/>
+   <properties entry="config/termsLayer.properties"/>
+   <reference bind="setEntryArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.suite.ui.termsEntryArea)"/>
+</scr:component>
diff --git a/org.argeo.product.knowledge/bnd.bnd b/org.argeo.product.knowledge/bnd.bnd
new file mode 100644 (file)
index 0000000..d942709
--- /dev/null
@@ -0,0 +1,9 @@
+Service-Component:\
+OSGI-INF/swtArgeoApp.xml,\
+OSGI-INF/termsEntryArea.xml,\
+OSGI-INF/termsLayer.xml,\
+
+Import-Package:\
+org.argeo.app.swt.ux,\
+org.argeo.app.swt.terms,\
+*
\ No newline at end of file
diff --git a/org.argeo.product.knowledge/build.properties b/org.argeo.product.knowledge/build.properties
new file mode 100644 (file)
index 0000000..fde2b82
--- /dev/null
@@ -0,0 +1,5 @@
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/swtArgeoApp.xml
+source.. = src/
+output.. = bin/
diff --git a/org.argeo.product.knowledge/config/swtArgeoApp.properties b/org.argeo.product.knowledge/config/swtArgeoApp.properties
new file mode 100644 (file)
index 0000000..96e1d31
--- /dev/null
@@ -0,0 +1,5 @@
+service.pid=argeo.product.knowledge.swtArgeoApp
+
+event.topics=argeo/suite/*
+
+argeo.cms.app.contextName=argeo/knowledge
\ No newline at end of file
diff --git a/org.argeo.product.knowledge/config/termsEntryArea.properties b/org.argeo.product.knowledge/config/termsEntryArea.properties
new file mode 100644 (file)
index 0000000..cd31517
--- /dev/null
@@ -0,0 +1 @@
+service.pid=argeo.suite.ui.termsEntryArea
diff --git a/org.argeo.product.knowledge/config/termsLayer.properties b/org.argeo.product.knowledge/config/termsLayer.properties
new file mode 100644 (file)
index 0000000..2c0532e
--- /dev/null
@@ -0,0 +1,5 @@
+service.pid=argeo.suite.ui.termsLayer
+title=Terms
+icon=dashboard
+
+entity.type=entity:terms,entity:term
\ No newline at end of file
index 82576bfe972cb05aaa969f96fd600087b798ba3c..6407f79570f8bb69fd6281f679779d6a2507af11 100644 (file)
@@ -19,10 +19,12 @@ org.argeo.cms.jcr
 argeo.osgi.start.5=\
 org.argeo.app.profile.acr.fs,\
 org.argeo.app.core,\
+org.argeo.app.jcr,\
 org.argeo.app.ui,\
 org.argeo.app.theme.default,\
 org.argeo.app.servlet.publish,\
-org.argeo.app.servlet.odk
+org.argeo.app.servlet.odk,\
+org.argeo.product.knowledge,\
 
 
 # Local
diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/TermsEntryArea.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/terms/TermsEntryArea.java
new file mode 100644 (file)
index 0000000..2193a3c
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.app.swt.terms;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+
+/** Entry area for managing the typologies. */
+public class TermsEntryArea implements SwtUiProvider {
+
+       @Override
+       public Control createUiPart(Composite parent, Content content) {
+               parent.setLayout(new GridLayout());
+               Label lbl = new Label(parent, SWT.NONE);
+               lbl.setText("Typologies");
+               return lbl;
+       }
+
+}
index 8a9cc954e8a39407fafea10c12c80accad896675..afa3f49d4f7d852c927a22b67eb6f69c707fdd10 100644 (file)
@@ -1 +1,3 @@
+appTitle=Argeo Suite
+
 people=People
diff --git a/swt/org.argeo.app.ui/OSGI-INF/termsEntryArea.xml b/swt/org.argeo.app.ui/OSGI-INF/termsEntryArea.xml
deleted file mode 100644 (file)
index 6387f1a..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Terms Entry Area">
-   <implementation class="org.argeo.app.ui.TermsEntryArea"/>
-   <service>
-      <provide interface="org.argeo.cms.swt.acr.SwtUiProvider"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/termsEntryArea.properties"/>
-</scr:component>
diff --git a/swt/org.argeo.app.ui/OSGI-INF/termsLayer.xml b/swt/org.argeo.app.ui/OSGI-INF/termsLayer.xml
deleted file mode 100644 (file)
index ecfea87..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Terms Layer">
-   <implementation class="org.argeo.app.swt.ux.DefaultEditionLayer"/>
-   <service>
-      <provide interface="org.argeo.app.swt.ux.SwtAppLayer"/>
-   </service>
-   <property name="service.ranking" type="Integer" value="-1000"/>
-   <properties entry="config/termsLayer.properties"/>
-   <reference bind="setEntryArea" cardinality="1..1" interface="org.argeo.cms.swt.acr.SwtUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.suite.ui.termsEntryArea)"/>
-</scr:component>
index fb2e3f72382239853355c5d22fc7b61fc25d9999..5bc2eead87a72c08a75f11d5867f23613e0c6d03 100644 (file)
@@ -7,8 +7,6 @@ OSGI-INF/leadPane.xml,\
 OSGI-INF/loginScreen.xml,\
 OSGI-INF/recentItems.xml,\
 OSGI-INF/adminLeadPane.xml,\
-OSGI-INF/termsEntryArea.xml,\
-OSGI-INF/termsLayer.xml,\
 OSGI-INF/dashboard.xml,\
 OSGI-INF/dashboardLayer.xml,\
 OSGI-INF/peopleEntryArea.xml,\
diff --git a/swt/org.argeo.app.ui/config/termsEntryArea.properties b/swt/org.argeo.app.ui/config/termsEntryArea.properties
deleted file mode 100644 (file)
index cd31517..0000000
+++ /dev/null
@@ -1 +0,0 @@
-service.pid=argeo.suite.ui.termsEntryArea
diff --git a/swt/org.argeo.app.ui/config/termsLayer.properties b/swt/org.argeo.app.ui/config/termsLayer.properties
deleted file mode 100644 (file)
index 2c0532e..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-service.pid=argeo.suite.ui.termsLayer
-title=Terms
-icon=dashboard
-
-entity.type=entity:terms,entity:term
\ No newline at end of file
index e16b7d6fe6ac4b86d7561f9893bd315a7d46fa47..3cc62104d1d32d7ce41ed27ac5ef796707847d92 100644 (file)
@@ -16,7 +16,7 @@ import javax.jcr.query.Query;
 import javax.jcr.query.QueryResult;
 
 import org.argeo.app.api.EntityType;
-import org.argeo.app.core.XPathUtils;
+import org.argeo.app.jcr.XPathUtils;
 import org.argeo.app.ui.widgets.DelayedText;
 import org.argeo.app.ux.SuiteIcon;
 import org.argeo.app.ux.SuiteUxEvent;
diff --git a/swt/org.argeo.app.ui/src/org/argeo/app/ui/TermsEntryArea.java b/swt/org.argeo.app.ui/src/org/argeo/app/ui/TermsEntryArea.java
deleted file mode 100644 (file)
index 97d8c1f..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.argeo.app.ui;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-
-/** Entry area for managing th etypologies. */
-public class TermsEntryArea implements CmsUiProvider {
-
-       @Override
-       public Control createUi(Composite parent, Node context) throws RepositoryException {
-               parent.setLayout(new GridLayout());
-               Label lbl = new Label(parent, SWT.NONE);
-               lbl.setText("Typologies");
-               return lbl;
-       }
-
-}
index 98b9c6c1eda244a774dcb438795bcedbafbb32f9..16b3f42c57c01220f91173212a6327a32c96b6ce 100644 (file)
@@ -1,8 +1,8 @@
 package org.argeo.app.ui.docbook;
 
 import static org.argeo.app.docbook.DbkType.para;
-import static org.argeo.app.docbook.DbkUtils.addDbk;
-import static org.argeo.app.docbook.DbkUtils.isDbk;
+import static org.argeo.app.jcr.docbook.DbkJcrUtils.addDbk;
+import static org.argeo.app.jcr.docbook.DbkJcrUtils.isDbk;
 
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -21,7 +21,7 @@ import org.argeo.api.cms.ux.Cms2DSize;
 import org.argeo.api.cms.ux.CmsEditable;
 import org.argeo.app.docbook.DbkAttr;
 import org.argeo.app.docbook.DbkType;
-import org.argeo.app.docbook.DbkUtils;
+import org.argeo.app.jcr.docbook.DbkJcrUtils;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.argeo.cms.swt.SwtEditablePart;
 import org.argeo.cms.ui.viewers.AbstractPageViewer;
@@ -265,7 +265,7 @@ public abstract class AbstractDbkViewer extends AbstractPageViewer implements Ke
 
        protected DbkSectionTitle prepareSectionTitle(Section newSection, String titleText) throws RepositoryException {
                Node sectionNode = newSection.getNode();
-               Node titleNode = DbkUtils.getOrAddDbk(sectionNode, DbkType.title);
+               Node titleNode = DbkJcrUtils.getOrAddDbk(sectionNode, DbkType.title);
                getTextInterpreter().write(titleNode, titleText);
                if (newSection.getHeader() == null)
                        newSection.createHeader();
@@ -685,7 +685,7 @@ public abstract class AbstractDbkViewer extends AbstractPageViewer implements Ke
                                                ((Control) sp).dispose();
                                }
                                // create title
-                               Node titleNode = DbkUtils.addDbk(newSectionNode, DbkType.title);
+                               Node titleNode = DbkJcrUtils.addDbk(newSectionNode, DbkType.title);
                                // newSectionNode.addNode(DocBookType.TITLE, DocBookType.TITLE);
                                getTextInterpreter().write(titleNode, txt);
 
index 1673bd8743fdda242a346b22c57cd444e3ab9175..89b5493ad50c7ec3889a8df0e52e48c6181b73b2 100644 (file)
@@ -7,7 +7,7 @@ import javax.jcr.Node;
 
 import org.argeo.api.cms.ux.CmsEditable;
 import org.argeo.app.docbook.DbkMsg;
-import org.argeo.app.docbook.DbkUtils;
+import org.argeo.app.jcr.docbook.DbkJcrUtils;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.argeo.cms.swt.SwtEditablePart;
 import org.argeo.cms.swt.MouseDown;
@@ -136,7 +136,7 @@ class DbkContextMenu {
                Label insertPictureB = new Label(parent, SWT.NONE);
                insertPictureB.setText(DbkMsg.insertPicture.lead());
                insertPictureB.addMouseListener((MouseDown) (e) -> {
-                       Node newNode = DbkUtils.insertImageAfter(nodePart.getNode());
+                       Node newNode = DbkJcrUtils.insertImageAfter(nodePart.getNode());
                        Jcr.save(newNode);
                        textViewer.insertPart(section, newNode);
                        hide();
@@ -144,7 +144,7 @@ class DbkContextMenu {
                Label insertVideoB = new Label(parent, SWT.NONE);
                insertVideoB.setText(DbkMsg.insertVideo.lead());
                insertVideoB.addMouseListener((MouseDown) (e) -> {
-                       Node newNode = DbkUtils.insertVideoAfter(nodePart.getNode());
+                       Node newNode = DbkJcrUtils.insertVideoAfter(nodePart.getNode());
                        Jcr.save(newNode);
                        textViewer.insertPart(section, newNode);
                        hide();
index 1493223499b0e78e40bb469c1b7458a97b4c5071..fef7a025260ebb9c0a07fb7b55f4e268b4d5fa96 100644 (file)
@@ -23,7 +23,7 @@ import org.argeo.app.api.EntityNames;
 import org.argeo.app.api.EntityType;
 import org.argeo.app.docbook.DbkAttr;
 import org.argeo.app.docbook.DbkType;
-import org.argeo.app.docbook.DbkUtils;
+import org.argeo.app.jcr.docbook.DbkJcrUtils;
 import org.argeo.cms.ui.util.CmsUiUtils;
 import org.argeo.cms.ui.util.DefaultImageManager;
 import org.argeo.jcr.JcrException;
@@ -55,7 +55,7 @@ public class DbkImageManager extends DefaultImageManager {
        @Override
        public Binary getImageBinary(Node node) {
                Node fileNode = null;
-               if (DbkUtils.isDbk(node, DbkType.mediaobject)) {
+               if (DbkJcrUtils.isDbk(node, DbkType.mediaobject)) {
                        Node imageDataNode = getImageDataNode(node);
                        fileNode = getFileNode(imageDataNode);
                }
index 80f768cb8fd0fcaef099a5c7fa5a58b47ac4b825..4875f7da7539551733e2c9b1b5934673f8ac119b 100644 (file)
@@ -2,7 +2,7 @@ package org.argeo.app.ui.docbook;
 
 import static org.argeo.app.docbook.DbkType.para;
 import static org.argeo.app.docbook.DbkType.title;
-import static org.argeo.app.docbook.DbkUtils.isDbk;
+import static org.argeo.app.jcr.docbook.DbkJcrUtils.isDbk;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
index c911700f9454533fe4d618f59c7b59170516bcd0..a0944d5e77f464f51ecb2883e1763e0e33be2295 100644 (file)
@@ -12,7 +12,7 @@ import javax.jcr.RepositoryException;
 import org.argeo.api.acr.ldap.NamingUtils;
 import org.argeo.app.docbook.DbkAttr;
 import org.argeo.app.docbook.DbkType;
-import org.argeo.app.docbook.DbkUtils;
+import org.argeo.app.jcr.docbook.DbkJcrUtils;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.argeo.cms.swt.Selected;
 import org.argeo.cms.ui.viewers.NodePart;
@@ -70,7 +70,7 @@ public class DbkVideo extends StyledControl implements SectionPart, NodePart {
                        Composite editor = new Composite(wrapper, SWT.BORDER);
                        editor.setLayout(new GridLayout(3, false));
                        editor.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-                       String fileref = DbkUtils.getMediaFileref(mediaobject);
+                       String fileref = DbkJcrUtils.getMediaFileref(mediaobject);
                        Text text = new Text(editor, SWT.SINGLE);
                        if (fileref != null)
                                text.setText(fileref);
@@ -171,7 +171,7 @@ public class DbkVideo extends StyledControl implements SectionPart, NodePart {
                        if (control instanceof Browser) {
                                Browser browser = (Browser) control;
                                getNode().getSession();
-                               String fileref = DbkUtils.getMediaFileref(getNode());
+                               String fileref = DbkJcrUtils.getMediaFileref(getNode());
                                if (fileref != null) {
                                        // TODO manage self-hosted videos
                                        // TODO for YouTube videos, check whether the URL starts with
index 7d41117a29fb3ce26beafb750383270fcadf6958..330abd23e441c469fc849e5be30f22e97460b70c 100644 (file)
@@ -5,7 +5,7 @@ import javax.jcr.RepositoryException;
 
 import org.argeo.api.cms.ux.CmsEditable;
 import org.argeo.app.docbook.DbkType;
-import org.argeo.app.docbook.DbkUtils;
+import org.argeo.app.jcr.docbook.DbkJcrUtils;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.eclipse.swt.widgets.Composite;
 
@@ -22,7 +22,7 @@ public class DocumentTextEditor extends AbstractDbkViewer {
        @Override
        protected void initModel(Node textNode) throws RepositoryException {
                if (isFlat()) {
-                       DbkUtils.addParagraph(textNode, "");
+                       DbkJcrUtils.addParagraph(textNode, "");
                }
 //             else
 //                     textNode.setProperty(DocBookNames.DBK_TITLE, textNode.getName());