]> code.delx.au - gnu-emacs/blobdiff - lisp/org/org-taskjuggler.el
Merge from emacs-24 branch
[gnu-emacs] / lisp / org / org-taskjuggler.el
index da9e156870eb43ba43b403a964a4b3a9e920a91c..4409013589f4f125ccf7c61f4df866e8233af257 100644 (file)
@@ -1,10 +1,9 @@
 ;;; org-taskjuggler.el --- TaskJuggler exporter for org-mode
 ;;
 ;;; 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
 ;;
 ;; Emacs Lisp Archive Entry
 ;; Filename: org-taskjuggler.el
-;; Version: 7.01
 ;; Author: Christian Egli
 ;; Maintainer: Christian Egli
 ;; Keywords: org, taskjuggler, project planning
 ;; Author: Christian Egli
 ;; Maintainer: Christian Egli
 ;; Keywords: org, taskjuggler, project planning
 ;; "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'
 ;; "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
 ;; TaskJugglerUI.
 ;;
 ;; * Resources
-;; 
+;;
 ;; Next you can define resources and assign those to work on specific
 ;; tasks. You can group your resources hierarchically. Tag the top
 ;; node of the resources with "taskjuggler_resource" (or whatever you
 ;; Next you can define resources and assign those to work on specific
 ;; tasks. You can group your resources hierarchically. Tag the top
 ;; node of the resources with "taskjuggler_resource" (or whatever you
 ;; etc for tasks.
 ;;
 ;; * Dependencies
 ;; 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
 ;; 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
 ;;   :END:
 ;; ** Markup Guidelines
 ;;    :PROPERTIES:
 ;;   :END:
 ;; ** Markup Guidelines
 ;;    :PROPERTIES:
-;;    :Effort:   2.0
+;;    :Effort:   2d
 ;;    :END:
 ;; ** Workflow Guidelines
 ;;    :PROPERTIES:
 ;;    :END:
 ;; ** Workflow Guidelines
 ;;    :PROPERTIES:
-;;    :Effort:   2.0
+;;    :Effort:   2d
 ;;    :END:
 ;; * Presentation
 ;;   :PROPERTIES:
 ;;    :END:
 ;; * Presentation
 ;;   :PROPERTIES:
-;;   :Effort:   2.0
+;;   :Effort:   2d
 ;;   :BLOCKER:  training_material { gapduration 1d } some_other_task
 ;;   :END:
 ;;   :BLOCKER:  training_material { gapduration 1d } some_other_task
 ;;   :END:
-;; 
+;;
 ;;;; * TODO
 ;;   - Use SCHEDULED and DEADLINE information (not just start and end
 ;;     properties).
 ;;;; * TODO
 ;;   - Use SCHEDULED and DEADLINE information (not just start and end
 ;;     properties).
 (defcustom org-export-taskjuggler-extension ".tjp"
   "Extension of TaskJuggler files."
   :group 'org-export-taskjuggler
 (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
   :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
   :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)
 
   :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
 (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
   :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
 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)
 
   :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
   '("taskreport \"Gantt Chart\" {
   headline \"Project Gantt Chart\"
   columns hierarchindex, name, start, end, effort, duration, completed, chart
@@ -210,9 +220,10 @@ with `org-export-taskjuggler-project-tag'"
 }")
   "Default reports for the project."
   :group 'org-export-taskjuggler
 }")
   "Default reports for the project."
   :group 'org-export-taskjuggler
+  :version "24.1"
   :type '(repeat (string :tag "Report")))
 
   :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
 }
   "shift s40 \"Part time shift\" {
   workinghours wed, thu, fri off
 }
@@ -221,11 +232,12 @@ with `org-export-taskjuggler-project-tag'"
 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
 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. 
+include another TaskJuggler file.
 
 The global properties are inserted after the project declaration
 but before any resource and task declarations."
   :group 'org-export-taskjuggler
 
 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
   :type '(string :tag "Preamble"))
 
 ;;; Hooks
@@ -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
   (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
         (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
            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))
                      (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
         (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))
               ("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)
        (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")
       (org-taskjuggler-open-project (car tasks))
       (insert org-export-taskjuggler-default-global-properties)
       (insert "\n")
@@ -331,6 +346,10 @@ with the TaskJuggler GUI."
         (command (concat process-name " " file-name)))
     (start-process-shell-command process-name nil command)))
 
         (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."
 (defun org-taskjuggler-parent-is-ordered-p ()
   "Return true if the parent of the current node has a property
 \"ORDERED\". Return nil otherwise."
@@ -344,7 +363,9 @@ information, all the properties, etc."
   (let* ((props (org-entry-properties))
         (components (org-heading-components))
         (level (nth 1 components))
   (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)
         (parent-ordered (org-taskjuggler-parent-is-ordered-p)))
     (push (cons "level" level) props)
     (push (cons "headline" headline) props)
@@ -362,16 +383,16 @@ a path to the current task."
     (dolist (task tasks resolved-tasks)
       (let ((level (cdr (assoc "level" task))))
        (cond
     (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)))
          (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))
          (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))
          (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)))))))
 
        (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."
   "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)
 
 (defun org-taskjuggler-resolve-dependencies (tasks)
   (let ((previous-level 0)
@@ -405,24 +444,24 @@ unique id to each resource."
             (depends (cdr (assoc "depends" task)))
             (parent-ordered (cdr (assoc "parent-ordered" task)))
             (blocker (cdr (assoc "BLOCKER" task)))
             (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
              (and blocker (string-match "previous-sibling" blocker)))
             (dependencies
              (org-taskjuggler-resolve-explicit-dependencies
-              (append 
+              (append
                (and depends (org-taskjuggler-tokenize-dependencies depends))
                (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
        (cond
               tasks))
              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))
          (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))
          (dotimes (tmp (- previous-level level))
            (pop siblings))
          (setq previous-sibling (car siblings))
@@ -432,7 +471,7 @@ unique id to each resource."
        (when (or (and previous-sibling parent-ordered) blocked-on-previous)
          (push (format "!%s" (cdr (assoc "unique-id" previous-sibling))) dependencies))
        ; store dependency information
        (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 
+       (when dependencies
          (push (cons "depends" (mapconcat 'identity dependencies ", ")) task))
        (setq previous-level level)
        (setq resolved-tasks (append resolved-tasks (list task)))))))
          (push (cons "depends" (mapconcat 'identity dependencies ", ")) task))
        (setq previous-level level)
        (setq resolved-tasks (append resolved-tasks (list task)))))))
@@ -442,10 +481,10 @@ unique id to each resource."
 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_]+'."
 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 
+  (cond
    ((string-match "^ *$" dependencies) nil)
    ((string-match "^[ \t]*\\([-a-zA-Z0-9_]+\\([ \t]*{[^}]+}\\)?\\)[ \t,]*" dependencies)
    ((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)))))
      (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)))))
@@ -459,27 +498,27 @@ where a matching tasks was found. If the dependency is
 `org-taskjuggler-resolve-dependencies'). If there is no matching
 task the dependency is ignored and a warning is displayed ."
   (unless (null dependencies)
 `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)))
        ;; 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)))
          (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
        ;; 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) " ")
        (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
               (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))))))
 
           (format "No task with matching property \"task_id\" found for id %s" id))
          (org-taskjuggler-resolve-explicit-dependencies (cdr dependencies) tasks))))))
 
@@ -488,7 +527,7 @@ task the dependency is ignored and a warning is displayed ."
 return nil."
   (let ((task-id (cdr (assoc "task_id" (car tasks))))
        (path (cdr (assoc "path" (car tasks)))))
 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))))))
      ((null tasks) nil)
      ((equal task-id id) path)
      (t (org-taskjuggler-find-task-with-id id (cdr tasks))))))
@@ -503,16 +542,21 @@ finally add more underscore characters (\"_\")."
         (parts (split-string headline))
         (id (org-taskjuggler-clean-id (downcase (pop parts)))))
     ; try to add more parts of the headline to make it unique
         (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)
+    (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 "_"
     (while (member id unique-ids)
       (setq id (concat id "_")))
     id))
       (setq id (concat id "_" (org-taskjuggler-clean-id (downcase (pop parts))))))
     ; 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."
 (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
 
 (defun org-taskjuggler-open-project (project)
   "Insert the beginning of a project declaration. All valid
@@ -520,11 +564,11 @@ 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)))
 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))))
      (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"))))
 with separator \"\n\"."
   (let ((filtered-items (remq nil items)))
     (and filtered-items (mapconcat 'identity filtered-items "\n"))))
-  
+
 (defun org-taskjuggler-get-attributes (item attributes)
 (defun org-taskjuggler-get-attributes (item attributes)
-  "Return all attribute as a single formated string. ITEM is an
+  "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."
 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
    (mapcar
-    (lambda (attribute) 
-      (org-taskjuggler-filter-and-join 
+    (lambda (attribute)
+      (org-taskjuggler-filter-and-join
        (org-taskjuggler-get-attribute item attribute)))
     attributes)))
 
        (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."
   "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)))
    ((null item) nil)
    ((equal (symbol-name attribute) (car (car item)))
     (cons (format "%s %s" (symbol-name attribute) (cdr (car item)))
@@ -565,74 +609,77 @@ defines a property \"resource_id\" it will be used as the id for
 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'."
 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)))
                 (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,
       "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)
    ((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."
 
 (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)))
            (- 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
     (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)))
       (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 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)
       (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)
     (insert "}\n")
     (setq org-export-taskjuggler-old-level (1- org-export-taskjuggler-old-level)))
   (when (= org-export-taskjuggler-old-level level)