]> code.delx.au - gnu-emacs/blobdiff - lisp/org/org-taskjuggler.el
Sync Org 7.9.2 from the commit tagged "release_7.9.2" in Org's Git repo.
[gnu-emacs] / lisp / org / org-taskjuggler.el
index da9e156870eb43ba43b403a964a4b3a9e920a91c..aa645d296e8cada2f5a6a6404ac2ba7975cfaa06 100644 (file)
@@ -1,10 +1,9 @@
 ;;; org-taskjuggler.el --- TaskJuggler exporter for org-mode
 ;;
-;; Copyright (C) 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
+;; Copyright (C) 2007-2012 Free Software Foundation, Inc.
 ;;
 ;; Emacs Lisp Archive Entry
 ;; Filename: org-taskjuggler.el
-;; Version: 7.01
 ;; Author: Christian Egli
 ;; Maintainer: Christian Egli
 ;; Keywords: org, taskjuggler, project planning
@@ -30,7 +29,7 @@
 ;;
 ;; This library implements a TaskJuggler exporter for org-mode.
 ;; TaskJuggler uses a text format to define projects, tasks and
-;; resources, so it is a natural fit for org-mode. It can produce all
+;; resources, so it is a natural fit for org-mode.  It can produce all
 ;; sorts of reports for tasks or resources in either HTML, CSV or PDF.
 ;; The current version of TaskJuggler requires KDE but the next
 ;; version is implemented in Ruby and should therefore run on any
@@ -43,7 +42,7 @@
 ;;
 ;; Instead the TaskJuggler exporter looks for a tree that defines the
 ;; tasks and a optionally tree that defines the resources for this
-;; project. It then creates a TaskJuggler file based on these trees
+;; project.  It then creates a TaskJuggler file based on these trees
 ;; and the attributes defined in all the nodes.
 ;;
 ;; * Installation
@@ -61,8 +60,8 @@
 ;;
 ;; * Tasks
 ;;
-;; Let's illustrate the usage with a small example. Create your tasks
-;; as you usually do with org-mode. Assign efforts to each task using
+;; Let's illustrate the usage with a small example.  Create your tasks
+;; as you usually do with org-mode.  Assign efforts to each task using
 ;; properties (it's easiest to do this in the column view). You should
 ;; end up with something similar to the example by Peter Jones in
 ;; http://www.contextualdevelopment.com/static/artifacts/articles/2008/project-planning/project-planning.org.
 ;; "taskjuggler_project" (or whatever you customized
 ;; `org-export-taskjuggler-project-tag' to). You are now ready to
 ;; export the project plan with `org-export-as-taskjuggler-and-open'
-;; which will export the project plan and open a gant chart in
+;; which will export the project plan and open a Gantt chart in
 ;; TaskJugglerUI.
 ;;
 ;; * Resources
-;; 
+;;
 ;; Next you can define resources and assign those to work on specific
-;; tasks. You can group your resources hierarchically. Tag the top
+;; tasks.  You can group your resources hierarchically.  Tag the top
 ;; node of the resources with "taskjuggler_resource" (or whatever you
 ;; customized `org-export-taskjuggler-resource-tag' to). You can
 ;; optionally assign an identifier (named "resource_id") to the
@@ -85,8 +84,8 @@
 ;; picks the first word of the headline as the identifier as long as
 ;; it is unique, see the documentation of
 ;; `org-taskjuggler-get-unique-id'). Using that identifier you can
-;; then allocate resources to tasks. This is again done with the
-;; "allocate" property on the tasks. Do this in column view or when on
+;; then allocate resources to tasks.  This is again done with the
+;; "allocate" property on the tasks.  Do this in column view or when on
 ;; the task type
 ;;
 ;;  C-c C-x p allocate RET <resource_id> RET
 ;; etc for tasks.
 ;;
 ;; * Dependencies
-;; 
+;;
 ;; The exporter will handle dependencies that are defined in the tasks
 ;; either with the ORDERED attribute (see TODO dependencies in the Org
 ;; mode manual) or with the BLOCKER attribute (see org-depend.el) or
-;; alternatively with a depends attribute. Both the BLOCKER and the
+;; alternatively with a depends attribute.  Both the BLOCKER and the
 ;; depends attribute can be either "previous-sibling" or a reference
 ;; to an identifier (named "task_id") which is defined for another
-;; task in the project. BLOCKER and the depends attribute can define
-;; multiple dependencies separated by either space or comma. You can
+;; task in the project.  BLOCKER and the depends attribute can define
+;; multiple dependencies separated by either space or comma.  You can
 ;; also specify optional attributes on the dependency by simply
-;; appending it. The following examples should illustrate this:
+;; appending it.  The following examples should illustrate this:
 ;;
 ;; * Training material
 ;;   :PROPERTIES:
 ;;   :END:
 ;; ** Markup Guidelines
 ;;    :PROPERTIES:
-;;    :Effort:   2.0
+;;    :Effort:   2d
 ;;    :END:
 ;; ** Workflow Guidelines
 ;;    :PROPERTIES:
-;;    :Effort:   2.0
+;;    :Effort:   2d
 ;;    :END:
 ;; * Presentation
 ;;   :PROPERTIES:
-;;   :Effort:   2.0
+;;   :Effort:   2d
 ;;   :BLOCKER:  training_material { gapduration 1d } some_other_task
 ;;   :END:
-;; 
+;;
 ;;;; * TODO
 ;;   - Use SCHEDULED and DEADLINE information (not just start and end
 ;;     properties).
 ;;     org-global-properties-fixed
 ;;   - What about property inheritance and org-property-inherit-p?
 ;;   - Use TYPE_TODO as an way to assign resources
-;;   - Make sure multiple dependency definitions (i.e. BLOCKER on
+;;   - Make sure multiple dependency definitions (i.e.  BLOCKER on
 ;;     previous-sibling and on a specific task_id) in multiple
 ;;     attributes are properly exported.
 ;;
 (defcustom org-export-taskjuggler-extension ".tjp"
   "Extension of TaskJuggler files."
   :group 'org-export-taskjuggler
+  :version "24.1"
   :type 'string)
 
 (defcustom org-export-taskjuggler-project-tag "taskjuggler_project"
   "Tag, property or todo used to find the tree containing all
 the tasks for the project."
   :group 'org-export-taskjuggler
+  :version "24.1"
   :type 'string)
 
 (defcustom org-export-taskjuggler-resource-tag "taskjuggler_resource"
   "Tag, property or todo used to find the tree containing all the
 resources for the project."
   :group 'org-export-taskjuggler
+  :version "24.1"
   :type 'string)
 
+(defcustom org-export-taskjuggler-target-version 2.4
+  "Which version of TaskJuggler the exporter is targeting."
+  :group 'org-export-taskjuggler
+  :version "24.1"
+  :type 'number)
+
 (defcustom org-export-taskjuggler-default-project-version "1.0"
   "Default version string for the project."
   :group 'org-export-taskjuggler
+  :version "24.1"
   :type 'string)
 
 (defcustom org-export-taskjuggler-default-project-duration 280
@@ -191,9 +200,10 @@ resources for the project."
 in the root node of the task tree, i.e. the tree that has been marked
 with `org-export-taskjuggler-project-tag'"
   :group 'org-export-taskjuggler
+  :version "24.1"
   :type 'integer)
 
-(defcustom org-export-taskjuggler-default-reports 
+(defcustom org-export-taskjuggler-default-reports
   '("taskreport \"Gantt Chart\" {
   headline \"Project Gantt Chart\"
   columns hierarchindex, name, start, end, effort, duration, completed, chart
@@ -201,7 +211,7 @@ with `org-export-taskjuggler-project-tag'"
   hideresource 1
   loadunit shortauto
 }"
-"resourcereport \"Resource Graph\" {
+    "resourcereport \"Resource Graph\" {
   headline \"Resource Allocation Graph\"
   columns no, name, utilization, freeload, chart
   loadunit shortauto
@@ -210,22 +220,24 @@ with `org-export-taskjuggler-project-tag'"
 }")
   "Default reports for the project."
   :group 'org-export-taskjuggler
+  :version "24.1"
   :type '(repeat (string :tag "Report")))
 
-(defcustom org-export-taskjuggler-default-global-properties 
+(defcustom org-export-taskjuggler-default-global-properties
   "shift s40 \"Part time shift\" {
   workinghours wed, thu, fri off
 }
 "
-  "Default global properties for the project. Here you typically
+  "Default global properties for the project.  Here you typically
 define global properties such as shifts, accounts, rates,
-vacation, macros and flags. Any property that is allowed within
-the TaskJuggler file can be inserted. You could for example
-include another TaskJuggler file. 
+vacation, macros and flags.  Any property that is allowed within
+the TaskJuggler file can be inserted.  You could for example
+include another TaskJuggler file.
 
 The global properties are inserted after the project declaration
 but before any resource and task declarations."
   :group 'org-export-taskjuggler
+  :version "24.1"
   :type '(string :tag "Preamble"))
 
 ;;; Hooks
@@ -243,12 +255,12 @@ but before any resource and task declarations."
   "Export parts of the current buffer as a TaskJuggler file.
 The exporter looks for a tree with tag, property or todo that
 matches `org-export-taskjuggler-project-tag' and takes this as
-the tasks for this project. The first node of this tree defines
+the tasks for this project.  The first node of this tree defines
 the project properties such as project name and project period.
 If there is a tree with tag, property or todo that matches
 `org-export-taskjuggler-resource-tag' this three is taken as
-resources for the project. If no resources are specified, a
-default resource is created and allocated to the project. Also
+resources for the project.  If no resources are specified, a
+default resource is created and allocated to the project.  Also
 the taskjuggler project will be created with default reports as
 defined in `org-export-taskjuggler-default-reports'."
   (interactive)
@@ -257,14 +269,15 @@ defined in `org-export-taskjuggler-default-reports'."
   (setq-default org-done-keywords org-done-keywords)
   (let* ((tasks
          (org-taskjuggler-resolve-dependencies
-          (org-taskjuggler-assign-task-ids 
-           (org-map-entries 
-            '(org-taskjuggler-components) 
-            org-export-taskjuggler-project-tag nil 'archive 'comment))))
+          (org-taskjuggler-assign-task-ids
+           (org-taskjuggler-compute-task-leafiness
+            (org-map-entries
+             'org-taskjuggler-components
+             org-export-taskjuggler-project-tag nil 'archive 'comment)))))
         (resources
          (org-taskjuggler-assign-resource-ids
-          (org-map-entries 
-           '(org-taskjuggler-components) 
+          (org-map-entries
+           'org-taskjuggler-components
            org-export-taskjuggler-resource-tag nil 'archive 'comment)))
         (filename (expand-file-name
                    (concat
@@ -272,15 +285,16 @@ defined in `org-export-taskjuggler-default-reports'."
                      (file-name-nondirectory buffer-file-name))
                     org-export-taskjuggler-extension)))
         (buffer (find-file-noselect filename))
+        (old-buffer (current-buffer))
         (org-export-taskjuggler-old-level 0)
         task resource)
     (unless tasks
       (error "No tasks specified"))
     ;; add a default resource
     (unless resources
-      (setq resources 
-           `((("resource_id" . ,(user-login-name)) 
-              ("headline" . ,user-full-name) 
+      (setq resources
+           `((("resource_id" . ,(user-login-name))
+              ("headline" . ,user-full-name)
               ("level" . 1)))))
     ;; add a default allocation to the first task if none was given
     (unless (assoc "allocate" (car tasks))
@@ -299,6 +313,7 @@ defined in `org-export-taskjuggler-default-reports'."
        (setcar tasks (push (cons "version" version) task))))
     (with-current-buffer buffer
       (erase-buffer)
+      (org-clone-local-variables old-buffer "^org-")
       (org-taskjuggler-open-project (car tasks))
       (insert org-export-taskjuggler-default-global-properties)
       (insert "\n")
@@ -331,9 +346,13 @@ with the TaskJuggler GUI."
         (command (concat process-name " " file-name)))
     (start-process-shell-command process-name nil command)))
 
+(defun org-taskjuggler-targeting-tj3-p ()
+  "Return true if we are targeting TaskJuggler III."
+  (>= org-export-taskjuggler-target-version 3.0))
+
 (defun org-taskjuggler-parent-is-ordered-p ()
   "Return true if the parent of the current node has a property
-\"ORDERED\". Return nil otherwise."
+\"ORDERED\".  Return nil otherwise."
   (save-excursion
     (and (org-up-heading-safe) (org-entry-get (point) "ORDERED"))))
 
@@ -344,7 +363,9 @@ information, all the properties, etc."
   (let* ((props (org-entry-properties))
         (components (org-heading-components))
         (level (nth 1 components))
-        (headline (nth 4 components))
+        (headline
+         (replace-regexp-in-string
+          "\"" "\\\"" (nth 4 components) t t)) ; quote double quotes in headlines
         (parent-ordered (org-taskjuggler-parent-is-ordered-p)))
     (push (cons "level" level) props)
     (push (cons "headline" headline) props)
@@ -352,7 +373,7 @@ information, all the properties, etc."
 
 (defun org-taskjuggler-assign-task-ids (tasks)
   "Given a list of tasks return the same list assigning a unique id
-and the full path to each task. Taskjuggler takes hierarchical ids.
+and the full path to each task.  Taskjuggler takes hierarchical ids.
 For that reason we have to make ids locally unique and we have to keep
 a path to the current task."
   (let ((previous-level 0)
@@ -362,16 +383,16 @@ a path to the current task."
     (dolist (task tasks resolved-tasks)
       (let ((level (cdr (assoc "level" task))))
        (cond
-        ((< previous-level level) 
+        ((< previous-level level)
          (setq unique-id (org-taskjuggler-get-unique-id task (car unique-ids)))
          (dotimes (tmp (- level previous-level))
            (push (list unique-id) unique-ids)
            (push unique-id path)))
-        ((= previous-level level) 
+        ((= previous-level level)
          (setq unique-id (org-taskjuggler-get-unique-id task (car unique-ids)))
          (push unique-id (car unique-ids))
          (setcar path unique-id))
-        ((> previous-level level) 
+        ((> previous-level level)
          (dotimes (tmp (- previous-level level))
            (pop unique-ids)
            (pop path))
@@ -383,18 +404,36 @@ a path to the current task."
        (setq previous-level level)
        (setq resolved-tasks (append resolved-tasks (list task)))))))
 
-(defun org-taskjuggler-assign-resource-ids (resources &optional unique-ids)
+(defun org-taskjuggler-compute-task-leafiness (tasks)
+  "Figure out if each task is a leaf by looking at it's level,
+and the level of its successor.  If the successor is higher (ie
+deeper), then it's not a leaf."
+  (let (new-list)
+    (while (car tasks)
+      (let ((task (car tasks))
+           (successor (car (cdr tasks))))
+       (cond
+        ;; if a task has no successors it is a leaf
+        ((null successor)
+         (push (cons (cons "leaf-node" t) task) new-list))
+        ;; if the successor has a lower level than task it is a leaf
+        ((<= (cdr (assoc "level" successor)) (cdr (assoc "level" task)))
+         (push (cons (cons "leaf-node" t) task) new-list))
+        ;; otherwise examine the rest of the tasks
+        (t (push task new-list))))
+      (setq tasks (cdr tasks)))
+    (nreverse new-list)))
+
+(defun org-taskjuggler-assign-resource-ids (resources)
   "Given a list of resources return the same list, assigning a
 unique id to each resource."
-  (cond
-   ((null resources) nil)
-   (t 
-    (let* ((resource (car resources))
-          (unique-id (org-taskjuggler-get-unique-id resource unique-ids)))
-      (push (cons "unique-id" unique-id) resource)
-      (cons resource 
-           (org-taskjuggler-assign-resource-ids (cdr resources) 
-                                                (cons unique-id unique-ids)))))))
+  (let (unique-ids new-list)
+    (dolist (resource resources new-list)
+      (let ((unique-id (org-taskjuggler-get-unique-id resource unique-ids)))
+       (push (cons "unique-id" unique-id) resource)
+       (push unique-id unique-ids)
+       (push resource new-list)))
+    (nreverse new-list)))
 
 (defun org-taskjuggler-resolve-dependencies (tasks)
   (let ((previous-level 0)
@@ -405,34 +444,34 @@ unique id to each resource."
             (depends (cdr (assoc "depends" task)))
             (parent-ordered (cdr (assoc "parent-ordered" task)))
             (blocker (cdr (assoc "BLOCKER" task)))
-            (blocked-on-previous 
+            (blocked-on-previous
              (and blocker (string-match "previous-sibling" blocker)))
             (dependencies
              (org-taskjuggler-resolve-explicit-dependencies
-              (append 
+              (append
                (and depends (org-taskjuggler-tokenize-dependencies depends))
-               (and blocker (org-taskjuggler-tokenize-dependencies blocker))) 
+               (and blocker (org-taskjuggler-tokenize-dependencies blocker)))
               tasks))
-             previous-sibling)
-       ; update previous sibling info
+            previous-sibling)
+                                       ; update previous sibling info
        (cond
-        ((< previous-level level) 
+        ((< previous-level level)
          (dotimes (tmp (- level previous-level))
            (push task siblings)))
         ((= previous-level level)
          (setq previous-sibling (car siblings))
          (setcar siblings task))
-        ((> previous-level level) 
+        ((> previous-level level)
          (dotimes (tmp (- previous-level level))
            (pop siblings))
          (setq previous-sibling (car siblings))
          (setcar siblings task)))
-       ; insert a dependency on previous sibling if the parent is
-       ; ordered or if the tasks has a BLOCKER attribute with value "previous-sibling"
+                                       ; insert a dependency on previous sibling if the parent is
+                                       ; ordered or if the tasks has a BLOCKER attribute with value "previous-sibling"
        (when (or (and previous-sibling parent-ordered) blocked-on-previous)
          (push (format "!%s" (cdr (assoc "unique-id" previous-sibling))) dependencies))
-       ; store dependency information
-       (when dependencies 
+                                       ; store dependency information
+       (when dependencies
          (push (cons "depends" (mapconcat 'identity dependencies ", ")) task))
        (setq previous-level level)
        (setq resolved-tasks (append resolved-tasks (list task)))))))
@@ -441,11 +480,11 @@ unique id to each resource."
   "Split a dependency property value DEPENDENCIES into the
 individual dependencies and return them as a list while keeping
 the optional arguments (such as gapduration) for the
-dependencies. A dependency will have to match `[-a-zA-Z0-9_]+'."
-  (cond 
+dependencies.  A dependency will have to match `[-a-zA-Z0-9_]+'."
+  (cond
    ((string-match "^ *$" dependencies) nil)
    ((string-match "^[ \t]*\\([-a-zA-Z0-9_]+\\([ \t]*{[^}]+}\\)?\\)[ \t,]*" dependencies)
-    (cons 
+    (cons
      (substring dependencies (match-beginning 1) (match-end 1))
      (org-taskjuggler-tokenize-dependencies (substring dependencies (match-end 0)))))
    (t (error (format "invalid dependency id %s" dependencies)))))
@@ -454,41 +493,41 @@ dependencies. A dependency will have to match `[-a-zA-Z0-9_]+'."
   "For each dependency in DEPENDENCIES try to find a
 corresponding task with a matching property \"task_id\" in TASKS.
 Return a list containing the resolved links for all DEPENDENCIES
-where a matching tasks was found. If the dependency is
+where a matching tasks was found.  If the dependency is
 \"previous-sibling\" it is ignored (as this is dealt with in
 `org-taskjuggler-resolve-dependencies'). If there is no matching
 task the dependency is ignored and a warning is displayed ."
   (unless (null dependencies)
-    (let* 
+    (let*
        ;; the dependency might have optional attributes such as "{
        ;; gapduration 5d }", so only use the first string as id for the
        ;; dependency
        ((dependency (car dependencies))
         (id (car (split-string dependency)))
-        (optional-attributes 
+        (optional-attributes
          (mapconcat 'identity (cdr (split-string dependency)) " "))
         (path (org-taskjuggler-find-task-with-id id tasks)))
-      (cond 
+      (cond
        ;; ignore previous sibling dependencies
        ((equal (car dependencies) "previous-sibling")
        (org-taskjuggler-resolve-explicit-dependencies (cdr dependencies) tasks))
        ;; if the id is found in another task use its path
-       ((not (null path)) 
+       ((not (null path))
        (cons (mapconcat 'identity (list path optional-attributes) " ")
-             (org-taskjuggler-resolve-explicit-dependencies 
+             (org-taskjuggler-resolve-explicit-dependencies
               (cdr dependencies) tasks)))
        ;; warn about dangling dependency but otherwise ignore it
-       (t (display-warning 
-          'org-export-taskjuggler 
+       (t (display-warning
+          'org-export-taskjuggler
           (format "No task with matching property \"task_id\" found for id %s" id))
          (org-taskjuggler-resolve-explicit-dependencies (cdr dependencies) tasks))))))
 
 (defun org-taskjuggler-find-task-with-id (id tasks)
-  "Find ID in tasks. If found return the path of task. Otherwise
+  "Find ID in tasks.  If found return the path of task.  Otherwise
 return nil."
   (let ((task-id (cdr (assoc "task_id" (car tasks))))
        (path (cdr (assoc "path" (car tasks)))))
-    (cond 
+    (cond
      ((null tasks) nil)
      ((equal task-id id) path)
      (t (org-taskjuggler-find-task-with-id id (cdr tasks))))))
@@ -502,29 +541,34 @@ finally add more underscore characters (\"_\")."
   (let* ((headline (cdr (assoc "headline" item)))
         (parts (split-string headline))
         (id (org-taskjuggler-clean-id (downcase (pop parts)))))
-    ; try to add more parts of the headline to make it unique
-    (while (member id unique-ids)
+                                       ; try to add more parts of the headline to make it unique
+    (while (and (member id unique-ids) (car parts))
       (setq id (concat id "_" (org-taskjuggler-clean-id (downcase (pop parts))))))
-    ; if its still not unique add "_"
+                                       ; if its still not unique add "_"
     (while (member id unique-ids)
       (setq id (concat id "_")))
     id))
-       
+
 (defun org-taskjuggler-clean-id (id)
   "Clean and return ID to make it acceptable for taskjuggler."
-  (and id (replace-regexp-in-string "[^a-zA-Z0-9_]" "_" id)))
+  (and id
+       ;; replace non-ascii by _
+       (replace-regexp-in-string
+       "[^a-zA-Z0-9_]" "_"
+       ;; make sure id doesn't start with a number
+       (replace-regexp-in-string "^\\([0-9]\\)" "_\\1" id))))
 
 (defun org-taskjuggler-open-project (project)
-  "Insert the beginning of a project declaration. All valid
-attributes from the PROJECT alist are inserted. If no end date is
+  "Insert the beginning of a project declaration.  All valid
+attributes from the PROJECT alist are inserted.  If no end date is
 specified it is calculated
 `org-export-taskjuggler-default-project-duration' days from now."
   (let* ((unique-id (cdr (assoc "unique-id" project)))
-       (headline (cdr (assoc "headline" project)))
-       (version (cdr (assoc "version" project)))
-       (start (cdr (assoc "start" project)))
-       (end (cdr (assoc "end" project))))
-    (insert 
+        (headline (cdr (assoc "headline" project)))
+        (version (cdr (assoc "version" project)))
+        (start (cdr (assoc "start" project)))
+        (end (cdr (assoc "end" project))))
+    (insert
      (format "project %s \"%s\" \"%s\" %s +%sd {\n }\n"
             unique-id headline version start
             org-export-taskjuggler-default-project-duration))))
@@ -534,16 +578,16 @@ specified it is calculated
 with separator \"\n\"."
   (let ((filtered-items (remq nil items)))
     (and filtered-items (mapconcat 'identity filtered-items "\n"))))
-  
+
 (defun org-taskjuggler-get-attributes (item attributes)
-  "Return all attribute as a single formated string. ITEM is an
-alist representing either a resource or a task. ATTRIBUTES is a
-list of symbols. Only entries from ITEM are considered that are
+  "Return all attribute as a single formatted string.  ITEM is an
+alist representing either a resource or a task.  ATTRIBUTES is a
+list of symbols.  Only entries from ITEM are considered that are
 listed in ATTRIBUTES."
-  (org-taskjuggler-filter-and-join 
+  (org-taskjuggler-filter-and-join
    (mapcar
-    (lambda (attribute) 
-      (org-taskjuggler-filter-and-join 
+    (lambda (attribute)
+      (org-taskjuggler-filter-and-join
        (org-taskjuggler-get-attribute item attribute)))
     attributes)))
 
@@ -551,7 +595,7 @@ listed in ATTRIBUTES."
   "Return a list of strings containing the properly formatted
 taskjuggler declaration for a given ATTRIBUTE in ITEM (an alist).
 If the ATTRIBUTE is not in ITEM return nil."
-  (cond 
+  (cond
    ((null item) nil)
    ((equal (symbol-name attribute) (car (car item)))
     (cons (format "%s %s" (symbol-name attribute) (cdr (car item)))
@@ -559,80 +603,83 @@ If the ATTRIBUTE is not in ITEM return nil."
    (t (org-taskjuggler-get-attribute (cdr item) attribute))))
 
 (defun org-taskjuggler-open-resource (resource)
-  "Insert the beginning of a resource declaration. All valid
-attributes from the RESOURCE alist are inserted. If the RESOURCE
+  "Insert the beginning of a resource declaration.  All valid
+attributes from the RESOURCE alist are inserted.  If the RESOURCE
 defines a property \"resource_id\" it will be used as the id for
-this resource. Otherwise it will use the ID property. If neither
+this resource.  Otherwise it will use the ID property.  If neither
 is defined it will calculate a unique id for the resource using
 `org-taskjuggler-get-unique-id'."
-  (let ((id (org-taskjuggler-clean-id 
-            (or (cdr (assoc "resource_id" resource)) 
-                (cdr (assoc "ID" resource)) 
+  (let ((id (org-taskjuggler-clean-id
+            (or (cdr (assoc "resource_id" resource))
+                (cdr (assoc "ID" resource))
                 (cdr (assoc "unique-id" resource)))))
        (headline (cdr (assoc "headline" resource)))
        (attributes '(limits vacation shift booking efficiency journalentry rate)))
-    (insert 
-     (concat 
+    (insert
+     (concat
       "resource " id " \"" headline "\" {\n "
       (org-taskjuggler-get-attributes resource attributes) "\n"))))
 
 (defun org-taskjuggler-clean-effort (effort)
   "Translate effort strings into a format acceptable to taskjuggler,
-i.e. REAL UNIT. If the effort string is something like 5:30 it
-will be assumed to be hours and will be translated into 5.5h.
-Otherwise if it contains something like 3.0 it is assumed to be
-days and will be translated into 3.0d. Other formats that
-taskjuggler supports (like weeks, months and years) are currently
-not supported."
-  (cond 
+i.e.  REAL UNIT. A valid effort string can be anything that is
+accepted by `org-duration-string-to-minutesĀ“."
+  (cond
    ((null effort) effort)
-   ((string-match "\\([0-9]+\\):\\([0-9]+\\)" effort) 
-    (let ((hours (string-to-number (match-string 1 effort)))
-         (minutes (string-to-number (match-string 2 effort))))
-      (format "%dh" (+ hours (/ minutes 60.0)))))
-   ((string-match "\\([0-9]+\\).\\([0-9]+\\)" effort) (concat effort "d"))
-   (t (error "Not a valid effort (%s)" effort))))
+   (t (let* ((minutes (org-duration-string-to-minutes effort))
+            (hours (/ minutes 60.0)))
+       (format "%.1fh" hours)))))
 
 (defun org-taskjuggler-get-priority (priority)
   "Return a priority between 1 and 1000 based on PRIORITY, an
 org-mode priority string."
-  (max 1 (/ (* 1000 (- org-lowest-priority (string-to-char priority))) 
+  (max 1 (/ (* 1000 (- org-lowest-priority (string-to-char priority)))
            (- org-lowest-priority org-highest-priority))))
 
 (defun org-taskjuggler-open-task (task)
   (let* ((unique-id (cdr (assoc "unique-id" task)))
-       (headline (cdr (assoc "headline" task)))
-       (effort (org-taskjuggler-clean-effort (cdr (assoc org-effort-property task))))
-       (depends (cdr (assoc "depends" task)))
-       (allocate (cdr (assoc "allocate" task)))
-       (priority-raw (cdr (assoc "PRIORITY" task)))
-       (priority (and priority-raw (org-taskjuggler-get-priority priority-raw)))
-       (state (cdr (assoc "TODO" task)))
-       (complete (or (and (member state org-done-keywords) "100") 
-                     (cdr (assoc "complete" task))))
-       (parent-ordered (cdr (assoc "parent-ordered" task)))
-       (previous-sibling (cdr (assoc "previous-sibling" task)))
-       (attributes 
-        '(account start note duration endbuffer endcredit end
-          flags journalentry length maxend maxstart milestone
-          minend minstart period reference responsible
-          scheduling startbuffer startcredit statusnote)))
+        (headline (cdr (assoc "headline" task)))
+        (effort (org-taskjuggler-clean-effort (cdr (assoc org-effort-property task))))
+        (depends (cdr (assoc "depends" task)))
+        (allocate (cdr (assoc "allocate" task)))
+        (priority-raw (cdr (assoc "PRIORITY" task)))
+        (priority (and priority-raw (org-taskjuggler-get-priority priority-raw)))
+        (state (cdr (assoc "TODO" task)))
+        (complete (or (and (member state org-done-keywords) "100")
+                      (cdr (assoc "complete" task))))
+        (parent-ordered (cdr (assoc "parent-ordered" task)))
+        (previous-sibling (cdr (assoc "previous-sibling" task)))
+        (milestone (or (cdr (assoc "milestone" task))
+                       (and (assoc "leaf-node" task)
+                            (not (or effort
+                                     (cdr (assoc "duration" task))
+                                     (cdr (assoc "end" task))
+                                     (cdr (assoc "period" task)))))))
+        (attributes
+         '(account start note duration endbuffer endcredit end
+                   flags journalentry length maxend maxstart minend
+                   minstart period reference responsible scheduling
+                   startbuffer startcredit statusnote)))
     (insert
-     (concat 
-      "task " unique-id " \"" headline "\" {\n" 
+     (concat
+      "task " unique-id " \"" headline "\" {\n"
       (if (and parent-ordered previous-sibling)
          (format " depends %s\n" previous-sibling)
        (and depends (format " depends %s\n" depends)))
-      (and allocate (format " purge allocations\n allocate %s\n" allocate))
+      (and allocate (format " purge %s\n allocate %s\n"
+                           (or (and (org-taskjuggler-targeting-tj3-p) "allocate")
+                               "allocations")
+                           allocate))
       (and complete (format " complete %s\n" complete))
       (and effort (format " effort %s\n" effort))
       (and priority (format " priority %s\n" priority))
-      
+      (and milestone (format " milestone\n"))
+
       (org-taskjuggler-get-attributes task attributes)
       "\n"))))
 
 (defun org-taskjuggler-close-maybe (level)
-  (while (> org-export-taskjuggler-old-level level) 
+  (while (> org-export-taskjuggler-old-level level)
     (insert "}\n")
     (setq org-export-taskjuggler-old-level (1- org-export-taskjuggler-old-level)))
   (when (= org-export-taskjuggler-old-level level)