Imported Upstream version 1.1.0 54/195154/1 upstream/1.1.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 11 Dec 2018 07:07:07 +0000 (16:07 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 11 Dec 2018 07:07:07 +0000 (16:07 +0900)
Change-Id: I45eeedb10a8d69f496e980a16d7fea7692c5b8e9
Signed-off-by: DongHun Kwak <dh0128.kwak@samsung.com>
12 files changed:
ChangeLog
Makefile.am
Makefile.in
NEWS
configure
configure.ac
its/Makefile.in
its/mallard.its
itstool
itstool.1 [new file with mode: 0644]
itstool.1.in [new file with mode: 0644]
itstool.in

index 4c034fa..23df2c4 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,309 @@
+commit 187fcbe585560f128c7436e66f6b8e3a789a73b0
+Author: Shaun McCance <shaunm@gnome.org>
+Date:   Sun Jun 26 12:28:10 2011 -0400
+
+    mallard.its: Set msgctxt on info titles
+
+ its/mallard.its |    3 +++
+ 1 files changed, 3 insertions(+), 0 deletions(-)
+
+commit b57dfa4327da4d889dbfc5d26d824482fd7b2f54
+Author: Shaun McCance <shaunm@gnome.org>
+Date:   Sun Jun 26 12:27:46 2011 -0400
+
+    Don't bomb if a locNotePointer returns a string
+
+ itstool.in |   22 +++++++++++++---------
+ 1 files changed, 13 insertions(+), 9 deletions(-)
+
+commit 8d97758ac43f2079d252fedf2a9666b33a751fd0
+Author: Shaun McCance <shaunm@gnome.org>
+Date:   Sat Jun 25 14:49:53 2011 -0400
+
+    Added itst:context to specify a msgctxt for a node
+
+ itstool.in           |   35 ++++++++++++++++++++++++++++++++++-
+ tests/Context.ll.po  |   35 +++++++++++++++++++++++++++++++++++
+ tests/Context.ll.xml |   15 +++++++++++++++
+ tests/Context.pot    |   35 +++++++++++++++++++++++++++++++++++
+ tests/Context.xml    |   14 ++++++++++++++
+ tests/run_tests.py   |    3 +++
+ 6 files changed, 136 insertions(+), 1 deletions(-)
+
+commit e84f296dd21de75b9244896be9d2acc6aeed4dea
+Author: Claude Paroz <claude@2xlibre.net>
+Date:   Fri Jun 24 12:25:17 2011 -0400
+
+    Fixes for Python 3
+
+ itstool.in         |    2 +-
+ tests/run_tests.py |    2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+commit b13436f2a75ffded500e5f26028237d337510394
+Author: Shaun McCance <shaunm@gnome.org>
+Date:   Fri Jun 24 11:21:38 2011 -0400
+
+    Use #!/usr/bin/python -s for shebang, RH bug #702989
+
+ itstool.in |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+commit d8b399ae628e822ee9a9762657969361c5aebc93
+Author: Shaun McCance <shaunm@gnome.org>
+Date:   Fri Jun 24 10:19:54 2011 -0400
+
+    Make itst:drop work on non-inline nodes
+
+ itstool.in         |   15 ++++++++++++++-
+ tests/Droprule.xml |    2 ++
+ 2 files changed, 16 insertions(+), 1 deletions(-)
+
+commit 56cd8382ae979fab1a4a4a25f052d93d1070fc6d
+Author: Shaun McCance <shaunm@gnome.org>
+Date:   Thu Jun 23 17:51:40 2011 -0400
+
+    Made dropRule take a drop attribute, like other rules
+
+ itstool.in            |    6 +++---
+ tests/Droprule.ll.xml |    2 +-
+ tests/Droprule.xml    |    2 +-
+ 3 files changed, 5 insertions(+), 5 deletions(-)
+
+commit 333218d4978a7b203252d35e7979f64cfb3b0c1c
+Author: Claude Paroz <claude@2xlibre.net>
+Date:   Tue May 31 22:46:29 2011 +0200
+
+    Add itst drop rule
+    
+    The itst Droprule is a rule allowing to ignore completely a tag from
+    the translation, including its content. The resulting xml will not
+    have the tag either.
+
+ itstool.in            |    7 ++++++-
+ tests/Droprule.ll.po  |   15 +++++++++++++++
+ tests/Droprule.ll.xml |   11 +++++++++++
+ tests/Droprule.pot    |   15 +++++++++++++++
+ tests/Droprule.xml    |   10 ++++++++++
+ tests/run_tests.py    |    4 ++++
+ 6 files changed, 61 insertions(+), 1 deletions(-)
+
+commit 7cda8e16e2281e0e6c42f1e47bfc98ee5e4fe4ae
+Author: Shaun McCance <shaunm@gnome.org>
+Date:   Thu Jun 23 17:45:16 2011 -0400
+
+    Renamed attribute test files
+
+ tests/Attributes1.ll.po  |   19 +++++++++++++++++++
+ tests/Attributes1.ll.xml |   10 ++++++++++
+ tests/Attributes1.pot    |   19 +++++++++++++++++++
+ tests/Attributes1.xml    |   10 ++++++++++
+ tests/README             |    2 --
+ tests/run_tests.py       |    4 ++--
+ tests/x-attr1.ll.po      |   19 -------------------
+ tests/x-attr1.ll.xml     |   10 ----------
+ tests/x-attr1.pot        |   19 -------------------
+ tests/x-attr1.xml        |   10 ----------
+ 10 files changed, 60 insertions(+), 62 deletions(-)
+
+commit 4334f863ae6ab636b3e7c905e15693a188e4bee1
+Author: Shaun McCance <shaunm@gnome.org>
+Date:   Thu Jun 23 16:11:42 2011 -0400
+
+    Handled translatable attributes in non-translatable elements
+
+ itstool.in           |   25 +++++++++++++++----------
+ tests/README         |    4 +++-
+ tests/run_tests.py   |    3 +++
+ tests/x-attr1.ll.po  |   19 +++++++++++++++++++
+ tests/x-attr1.ll.xml |   10 ++++++++++
+ tests/x-attr1.pot    |   19 +++++++++++++++++++
+ tests/x-attr1.xml    |   10 ++++++++++
+ 7 files changed, 79 insertions(+), 11 deletions(-)
+
+commit 7ecd70b27d5cc22f8d6b35fc68aa9fe9cd3a7def
+Author: Claude Paroz <claude@2xlibre.net>
+Date:   Sun Jun 12 21:40:36 2011 +0200
+
+    Extract and translate node attributes
+
+ itstool.in                   |   41 ++++++++++++++++++++++++++++++++++++-----
+ tests/Translate1.ll.po       |    4 ++++
+ tests/Translate1.ll.xml      |    2 +-
+ tests/Translate1.pot         |    4 ++++
+ tests/Translate2.ll.po       |    4 ++++
+ tests/Translate2.ll.xml      |    2 +-
+ tests/Translate2.pot         |    4 ++++
+ tests/TranslateGlobal.ll.po  |   23 +++++++++++++++++++++++
+ tests/TranslateGlobal.ll.xml |   13 +++++++++++++
+ tests/TranslateGlobal.pot    |   23 +++++++++++++++++++++++
+ tests/run_tests.py           |    7 +++----
+ 11 files changed, 116 insertions(+), 11 deletions(-)
+
+commit fadb43fc37dbe18d9b1204bb13fb5ccea492b649
+Merge: 43a518a 1ebcc2a
+Author: Shaun McCance <shaunm@gnome.org>
+Date:   Wed Jun 1 09:22:03 2011 -0400
+
+    Merge branch 'testsuite'
+
+commit 1ebcc2af7fa4d6e12687199e173720b44eb823a3
+Author: Claude Paroz <claude@2xlibre.net>
+Date:   Wed Jun 1 15:11:51 2011 +0200
+
+    Fix LocNote2 test and add README in tests
+
+ tests/LocNote2.pot |   25 +++++++++++++++++++++++++
+ tests/README       |    5 +++++
+ tests/run_tests.py |    4 ++--
+ 3 files changed, 32 insertions(+), 2 deletions(-)
+
+commit 43a518a2da4eb11f78b23cbca6291f9cd7c2f4ab
+Author: Shaun McCance <shaunm@gnome.org>
+Date:   Wed Jun 1 08:59:26 2011 -0400
+
+    Adding copyright and license info to itstool
+
+ itstool.in |   17 +++++++++++++++++
+ 1 files changed, 17 insertions(+), 0 deletions(-)
+
+commit 480a2531c9b8ba0249f9326e8d043de309486e5b
+Author: Claude Paroz <claude@2xlibre.net>
+Date:   Wed Jun 1 13:45:51 2011 +0200
+
+    Add remaining tests
+
+ itstool.in                                 |    5 ++-
+ tests/EX-locNotePointer-attribute-1.pot    |   21 +++++++++++
+ tests/EX-locNoteRefPointer-attribute-1.pot |   21 +++++++++++
+ tests/LocNote1.pot                         |   25 +++++++++++++
+ tests/LocNote3.pot                         |   29 ++++++++++++++++
+ tests/LocNote4.pot                         |   30 ++++++++++++++++
+ tests/WithinText1.ll.po                    |   23 ++++++++++++
+ tests/WithinText1.ll.xml                   |   14 ++++++++
+ tests/WithinText1.pot                      |   23 ++++++++++++
+ tests/WithinText2.ll.po                    |   51 ++++++++++++++++++++++++++++
+ tests/WithinText2.ll.xml                   |   21 +++++++++++
+ tests/WithinText2.pot                      |   51 ++++++++++++++++++++++++++++
+ tests/run_tests.py                         |   40 ++++++++++++++++++++-
+ 13 files changed, 350 insertions(+), 4 deletions(-)
+
+commit 66f3fdd0d678bfffefef4b26e0c6812e3a9b8bb8
+Author: Shaun McCance <shaunm@gnome.org>
+Date:   Wed Jun 1 08:07:42 2011 -0400
+
+    itstool: Allow both XLink and child rules on its:rules
+    
+    We weren't handling tests/WithinText2.xml correctly
+
+ itstool.in |    3 +--
+ 1 files changed, 1 insertions(+), 2 deletions(-)
+
+commit 2ffe1d30ea1d5925a75955bc9b88c3d4c14b7b46
+Author: Claude Paroz <claude@2xlibre.net>
+Date:   Tue May 31 22:05:00 2011 +0200
+
+    Add tests for the Translate* series
+
+ tests/Translate2.ll.po  |   15 +++++++++++++++
+ tests/Translate2.ll.xml |   10 ++++++++++
+ tests/Translate2.pot    |   15 +++++++++++++++
+ tests/Translate3.ll.po  |   19 +++++++++++++++++++
+ tests/Translate3.ll.xml |   10 ++++++++++
+ tests/Translate4.ll.po  |   19 +++++++++++++++++++
+ tests/Translate4.ll.xml |   10 ++++++++++
+ tests/Translate5.ll.po  |   19 +++++++++++++++++++
+ tests/Translate5.ll.xml |   19 +++++++++++++++++++
+ tests/Translate5.pot    |   19 +++++++++++++++++++
+ tests/Translate6.ll.po  |   31 +++++++++++++++++++++++++++++++
+ tests/Translate6.ll.xml |   19 +++++++++++++++++++
+ tests/Translate7.ll.po  |   19 +++++++++++++++++++
+ tests/Translate7.ll.xml |   29 +++++++++++++++++++++++++++++
+ tests/Translate7.pot    |   19 +++++++++++++++++++
+ tests/run_tests.py      |   41 +++++++++++++++++++++++++++++++++++------
+ 16 files changed, 307 insertions(+), 6 deletions(-)
+
+commit d3177444660bac67265ba9cf17d586fd1b0b2907
+Author: Claude Paroz <claude@2xlibre.net>
+Date:   Tue May 31 20:41:13 2011 +0200
+
+    Fix xml iteration when constructing translated subnodes
+
+ itstool.in              |    3 ++-
+ tests/Translate1.ll.xml |    2 +-
+ 2 files changed, 3 insertions(+), 2 deletions(-)
+
+commit 6c7b6bb9db8e77ec142fac2c9b514ec1769df65b
+Author: Claude Paroz <claude@2xlibre.net>
+Date:   Tue May 31 16:33:48 2011 +0200
+
+    Use unicode strings inside of Message class
+
+ itstool.in |   40 +++++++++++++++++++++-------------------
+ 1 files changed, 21 insertions(+), 19 deletions(-)
+
+commit f4635d084d93212af73221de5165c3fa92f834cd
+Author: Claude Paroz <claude@2xlibre.net>
+Date:   Mon May 30 21:23:15 2011 +0200
+
+    Initial test infrastructure
+
+ tests/Translate1.ll.po  |   35 +++++++++++++++++++++++++
+ tests/Translate1.ll.xml |   39 +++++++++++++++++++++++++++
+ tests/Translate1.pot    |   35 +++++++++++++++++++++++++
+ tests/run_tests.py      |   66 +++++++++++++++++++++++++++++++++++++++++++++++
+ 4 files changed, 175 insertions(+), 0 deletions(-)
+
+commit 00a8df54545a66aa70c20ceecb19709ca0b811a7
+Author: Shaun McCance <shaunm@gnome.org>
+Date:   Fri May 27 12:09:10 2011 -0400
+
+    Include installation dir in search path if XDG_DATA_DIRS not set
+
+ configure.ac |    9 +++++++++
+ itstool.in   |    5 ++++-
+ 2 files changed, 13 insertions(+), 1 deletions(-)
+
+commit abd3afc9bd7066658d295de3a39c5c0b287d0644
+Author: Shaun McCance <shaunm@gnome.org>
+Date:   Mon May 9 21:26:42 2011 -0400
+
+    itstool: Allow localization notes to be space-preserving
+
+ itstool.in |   32 ++++++++++++++++++++++----------
+ 1 files changed, 22 insertions(+), 10 deletions(-)
+
+commit 04a706ae1057d0a9ed4b36c7e20292d44e97ad80
+Author: Shaun McCance <shaunm@gnome.org>
+Date:   Mon May 9 16:32:25 2011 -0400
+
+    itstool.1: Added a man page
+
+ .gitignore   |    1 +
+ Makefile.am  |   10 +++++++-
+ configure.ac |    1 +
+ itstool.1.in |   77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 4 files changed, 88 insertions(+), 1 deletions(-)
+
+commit 69db1ded268af460d31b6ab2d86172873468890f
+Author: Shaun McCance <shaunm@gnome.org>
+Date:   Mon May 9 11:15:02 2011 -0400
+
+    Catch XPath exceptions and warn
+
+ itstool.in |   30 +++++++++++++++++++-----------
+ 1 files changed, 19 insertions(+), 11 deletions(-)
+
+commit 6fdaea81bf70023612202cadcd7d9995e571f23e
+Author: Shaun McCance <shaunm@gnome.org>
+Date:   Fri May 6 17:02:13 2011 -0400
+
+    Version 1.0.1
+
+ NEWS         |    7 +++++++
+ configure.ac |    2 +-
+ 2 files changed, 8 insertions(+), 1 deletions(-)
+
 commit e9344508508112ecd177232ba49b5664e860bc9f
 Author: Shaun McCance <shaunm@gnome.org>
 Date:   Tue May 3 12:48:53 2011 -0400
index 14d037e..c4efc15 100644 (file)
@@ -2,7 +2,15 @@ SUBDIRS = its
 
 bin_SCRIPTS = itstool
 
-EXTRA_DIST = ChangeLog COPYING.GPL3 $(bin_SCRIPTS) itstool.in
+man_MANS = itstool.1
+
+EXTRA_DIST =           \
+       ChangeLog       \
+       COPYING.GPL3    \
+       $(bin_SCRIPTS)  \
+       itstool.in      \
+       $(man_MANS)     \
+       itstool.1.in
 
 ChangeLog:
        @if test -f $(top_srcdir)/.git/HEAD; then \
index 8da4ea7..f1ba50d 100644 (file)
@@ -34,9 +34,9 @@ PRE_UNINSTALL = :
 POST_UNINSTALL = :
 subdir = .
 DIST_COMMON = README $(am__configure_deps) $(srcdir)/Makefile.am \
-       $(srcdir)/Makefile.in $(srcdir)/itstool.in \
-       $(top_srcdir)/configure AUTHORS COPYING ChangeLog INSTALL NEWS \
-       install-sh missing
+       $(srcdir)/Makefile.in $(srcdir)/itstool.1.in \
+       $(srcdir)/itstool.in $(top_srcdir)/configure AUTHORS COPYING \
+       ChangeLog INSTALL NEWS install-sh missing
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
 am__aclocal_m4_deps = $(top_srcdir)/configure.ac
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
@@ -44,7 +44,7 @@ am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
 am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
  configure.lineno config.status.lineno
 mkinstalldirs = $(install_sh) -d
-CONFIG_CLEAN_FILES = itstool
+CONFIG_CLEAN_FILES = itstool itstool.1
 CONFIG_CLEAN_VPATH_FILES =
 am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
 am__vpath_adj = case $$p in \
@@ -67,7 +67,7 @@ am__nobase_list = $(am__nobase_strip_setup); \
 am__base_list = \
   sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
   sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
-am__installdirs = "$(DESTDIR)$(bindir)"
+am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(man1dir)"
 SCRIPTS = $(bin_SCRIPTS)
 SOURCES =
 DIST_SOURCES =
@@ -78,6 +78,9 @@ RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
        install-pdf-recursive install-ps-recursive install-recursive \
        installcheck-recursive installdirs-recursive pdf-recursive \
        ps-recursive uninstall-recursive
+man1dir = $(mandir)/man1
+NROFF = nroff
+MANS = $(man_MANS)
 RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive        \
   distclean-recursive maintainer-clean-recursive
 AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \
@@ -129,6 +132,7 @@ AUTOHEADER = @AUTOHEADER@
 AUTOMAKE = @AUTOMAKE@
 AWK = @AWK@
 CYGPATH_W = @CYGPATH_W@
+DATADIR = @DATADIR@
 DEFS = @DEFS@
 ECHO_C = @ECHO_C@
 ECHO_N = @ECHO_N@
@@ -196,7 +200,15 @@ top_builddir = @top_builddir@
 top_srcdir = @top_srcdir@
 SUBDIRS = its
 bin_SCRIPTS = itstool
-EXTRA_DIST = ChangeLog COPYING.GPL3 $(bin_SCRIPTS) itstool.in
+man_MANS = itstool.1
+EXTRA_DIST = \
+       ChangeLog       \
+       COPYING.GPL3    \
+       $(bin_SCRIPTS)  \
+       itstool.in      \
+       $(man_MANS)     \
+       itstool.1.in
+
 all: all-recursive
 
 .SUFFIXES:
@@ -236,6 +248,8 @@ $(ACLOCAL_M4):  $(am__aclocal_m4_deps)
 $(am__aclocal_m4_deps):
 itstool: $(top_builddir)/config.status $(srcdir)/itstool.in
        cd $(top_builddir) && $(SHELL) ./config.status $@
+itstool.1: $(top_builddir)/config.status $(srcdir)/itstool.1.in
+       cd $(top_builddir) && $(SHELL) ./config.status $@
 install-binSCRIPTS: $(bin_SCRIPTS)
        @$(NORMAL_INSTALL)
        test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
@@ -270,6 +284,44 @@ uninstall-binSCRIPTS:
        test -n "$$list" || exit 0; \
        echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
        cd "$(DESTDIR)$(bindir)" && rm -f $$files
+install-man1: $(man_MANS)
+       @$(NORMAL_INSTALL)
+       test -z "$(man1dir)" || $(MKDIR_P) "$(DESTDIR)$(man1dir)"
+       @list=''; test -n "$(man1dir)" || exit 0; \
+       { for i in $$list; do echo "$$i"; done; \
+       l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \
+         sed -n '/\.1[a-z]*$$/p'; \
+       } | while read p; do \
+         if test -f $$p; then d=; else d="$(srcdir)/"; fi; \
+         echo "$$d$$p"; echo "$$p"; \
+       done | \
+       sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \
+             -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \
+       sed 'N;N;s,\n, ,g' | { \
+       list=; while read file base inst; do \
+         if test "$$base" = "$$inst"; then list="$$list $$file"; else \
+           echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man1dir)/$$inst'"; \
+           $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man1dir)/$$inst" || exit $$?; \
+         fi; \
+       done; \
+       for i in $$list; do echo "$$i"; done | $(am__base_list) | \
+       while read files; do \
+         test -z "$$files" || { \
+           echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man1dir)'"; \
+           $(INSTALL_DATA) $$files "$(DESTDIR)$(man1dir)" || exit $$?; }; \
+       done; }
+
+uninstall-man1:
+       @$(NORMAL_UNINSTALL)
+       @list=''; test -n "$(man1dir)" || exit 0; \
+       files=`{ for i in $$list; do echo "$$i"; done; \
+       l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \
+         sed -n '/\.1[a-z]*$$/p'; \
+       } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \
+             -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \
+       test -z "$$files" || { \
+         echo " ( cd '$(DESTDIR)$(man1dir)' && rm -f" $$files ")"; \
+         cd "$(DESTDIR)$(man1dir)" && rm -f $$files; }
 
 # This directory's subdirectories are mostly independent; you can cd
 # into them and run `make' without going through this Makefile.
@@ -407,6 +459,19 @@ distclean-tags:
        -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
 
 distdir: $(DISTFILES)
+       @list='$(MANS)'; if test -n "$$list"; then \
+         list=`for p in $$list; do \
+           if test -f $$p; then d=; else d="$(srcdir)/"; fi; \
+           if test -f "$$d$$p"; then echo "$$d$$p"; else :; fi; done`; \
+         if test -n "$$list" && \
+           grep 'ab help2man is required to generate this page' $$list >/dev/null; then \
+           echo "error: found man pages containing the \`missing help2man' replacement text:" >&2; \
+           grep -l 'ab help2man is required to generate this page' $$list | sed 's/^/         /' >&2; \
+           echo "       to fix them, install help2man, remove and regenerate the man pages;" >&2; \
+           echo "       typically \`make maintainer-clean' will remove them" >&2; \
+           exit 1; \
+         else :; fi; \
+       else :; fi
        $(am__remove_distdir)
        test -d "$(distdir)" || mkdir "$(distdir)"
        @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
@@ -582,10 +647,10 @@ distcleancheck: distclean
               exit 1; } >&2
 check-am: all-am
 check: check-recursive
-all-am: Makefile $(SCRIPTS)
+all-am: Makefile $(SCRIPTS) $(MANS)
 installdirs: installdirs-recursive
 installdirs-am:
-       for dir in "$(DESTDIR)$(bindir)"; do \
+       for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(man1dir)"; do \
          test -z "$$dir" || $(MKDIR_P) "$$dir"; \
        done
 install: install-recursive
@@ -634,7 +699,7 @@ info: info-recursive
 
 info-am:
 
-install-data-am:
+install-data-am: install-man
 
 install-dvi: install-dvi-recursive
 
@@ -650,7 +715,7 @@ install-info: install-info-recursive
 
 install-info-am:
 
-install-man:
+install-man: install-man1
 
 install-pdf: install-pdf-recursive
 
@@ -680,7 +745,9 @@ ps: ps-recursive
 
 ps-am:
 
-uninstall-am: uninstall-binSCRIPTS
+uninstall-am: uninstall-binSCRIPTS uninstall-man
+
+uninstall-man: uninstall-man1
 
 .MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) ctags-recursive \
        install-am install-strip tags-recursive
@@ -694,12 +761,13 @@ uninstall-am: uninstall-binSCRIPTS
        info-am install install-am install-binSCRIPTS install-data \
        install-data-am install-dvi install-dvi-am install-exec \
        install-exec-am install-html install-html-am install-info \
-       install-info-am install-man install-pdf install-pdf-am \
-       install-ps install-ps-am install-strip installcheck \
-       installcheck-am installdirs installdirs-am maintainer-clean \
-       maintainer-clean-generic mostlyclean mostlyclean-generic pdf \
-       pdf-am ps ps-am tags tags-recursive uninstall uninstall-am \
-       uninstall-binSCRIPTS
+       install-info-am install-man install-man1 install-pdf \
+       install-pdf-am install-ps install-ps-am install-strip \
+       installcheck installcheck-am installdirs installdirs-am \
+       maintainer-clean maintainer-clean-generic mostlyclean \
+       mostlyclean-generic pdf pdf-am ps ps-am tags tags-recursive \
+       uninstall uninstall-am uninstall-binSCRIPTS uninstall-man \
+       uninstall-man1
 
 
 ChangeLog:
diff --git a/NEWS b/NEWS
index 0806b07..32cdcc4 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,17 @@
+1.1.0
+=====
+* Added itst:context to set msgctxt
+* Added itst:drop to drop context from translations
+* Allow XML attribute to be translated
+* Allow locNotePointer to return a string
+* Allow localization notes to be space-preserving
+* Allow both XLink and child rules on its:rules
+* Fixed Unicode encoding/decoding errors
+* Added automated test suite
+* Added a man page
+* Python 3 fixes
+* Commits by Shaun McCance, Claude Paroz
+
 1.0.1
 =====
 * Convert POSIX-style locales to BCP47
index 59f81ee..738537a 100755 (executable)
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.66 for itstool 1.0.1.
+# Generated by GNU Autoconf 2.66 for itstool 1.1.0.
 #
 #
 # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
@@ -548,13 +548,14 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='itstool'
 PACKAGE_TARNAME='itstool'
-PACKAGE_VERSION='1.0.1'
-PACKAGE_STRING='itstool 1.0.1'
+PACKAGE_VERSION='1.1.0'
+PACKAGE_STRING='itstool 1.1.0'
 PACKAGE_BUGREPORT=''
 PACKAGE_URL=''
 
 ac_subst_vars='LTLIBOBJS
 LIBOBJS
+DATADIR
 am__untar
 am__tar
 AMTAR
@@ -1164,7 +1165,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures itstool 1.0.1 to adapt to many kinds of systems.
+\`configure' configures itstool 1.1.0 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1230,7 +1231,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of itstool 1.0.1:";;
+     short | recursive ) echo "Configuration of itstool 1.1.0:";;
    esac
   cat <<\_ACEOF
 
@@ -1297,7 +1298,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-itstool configure 1.0.1
+itstool configure 1.1.0
 generated by GNU Autoconf 2.66
 
 Copyright (C) 2010 Free Software Foundation, Inc.
@@ -1314,7 +1315,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by itstool $as_me 1.0.1, which was
+It was created by itstool $as_me 1.1.0, which was
 generated by GNU Autoconf 2.66.  Invocation command line was
 
   $ $0 $@
@@ -2129,7 +2130,7 @@ fi
 
 # Define the identity of the package.
  PACKAGE='itstool'
- VERSION='1.0.1'
+ VERSION='1.1.0'
 
 
 cat >>confdefs.h <<_ACEOF
@@ -2170,7 +2171,16 @@ am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -'
 
 
 
-ac_config_files="$ac_config_files Makefile itstool its/Makefile"
+DATADIR=`(
+       case $prefix in
+               NONE) prefix=$ac_default_prefix ;;
+               *) ;;
+       esac
+       eval echo $(eval echo $datadir)
+)`
+
+
+ac_config_files="$ac_config_files Makefile itstool itstool.1 its/Makefile"
 
 
 cat >confcache <<\_ACEOF
@@ -2716,7 +2726,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by itstool $as_me 1.0.1, which was
+This file was extended by itstool $as_me 1.1.0, which was
 generated by GNU Autoconf 2.66.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -2769,7 +2779,7 @@ _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-itstool config.status 1.0.1
+itstool config.status 1.1.0
 configured by $0, generated by GNU Autoconf 2.66,
   with options \\"\$ac_cs_config\\"
 
@@ -2878,6 +2888,7 @@ do
   case $ac_config_target in
     "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
     "itstool") CONFIG_FILES="$CONFIG_FILES itstool" ;;
+    "itstool.1") CONFIG_FILES="$CONFIG_FILES itstool.1" ;;
     "its/Makefile") CONFIG_FILES="$CONFIG_FILES its/Makefile" ;;
 
   *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
index 623317d..7601dfe 100644 (file)
@@ -1,9 +1,19 @@
-AC_INIT([itstool], [1.0.1], [])
+AC_INIT([itstool], [1.1.0], [])
 AM_INIT_AUTOMAKE([1.9 no-dist-gzip dist-bzip2])
 
+DATADIR=`(
+       case $prefix in
+               NONE) prefix=$ac_default_prefix ;;
+               *) ;;
+       esac
+       eval echo $(eval echo $datadir)
+)`
+AC_SUBST([DATADIR])
+
 AC_CONFIG_FILES([
 Makefile
 itstool
+itstool.1
 its/Makefile
 ])
 
index 31bfe58..3bfab15 100644 (file)
@@ -74,6 +74,7 @@ AUTOHEADER = @AUTOHEADER@
 AUTOMAKE = @AUTOMAKE@
 AWK = @AWK@
 CYGPATH_W = @CYGPATH_W@
+DATADIR = @DATADIR@
 DEFS = @DEFS@
 ECHO_C = @ECHO_C@
 ECHO_N = @ECHO_N@
index b0af697..eaf31c3 100644 (file)
@@ -36,4 +36,7 @@
   <its:translateRule translate="no" selector="//mal:credit/mal:email"/>
 
   <itst:externalRefRule selector="//mal:media" refPointer="@src"/>
+
+  <itst:contextRule selector="//mal:info/mal:title[@type]" contextPointer="@type"/>
+  <itst:contextRule selector="//mal:info/mal:title[@type and @role]" contextPointer="concat(@type, ':', @role)"/>
 </its:rules>
diff --git a/itstool b/itstool
index 80a2318..3492abd 100644 (file)
--- a/itstool
+++ b/itstool
@@ -1,6 +1,24 @@
-#!/usr/bin/env python
-
-VERSION="1.0.1"
+#!/usr/bin/python -s
+#
+# Copyright (c) 2010-2011 Shaun McCance <shaunm@gnome.org>
+#
+# ITS Tool program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# ITS Tool is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with ITS Tool; if not, write to the Free Software Foundation, 59 Temple
+# Place, Suite 330, Boston, MA  0211-1307  USA.
+#
+
+VERSION="1.1.0"
+DATADIR="/usr/local/share"
 
 import gettext
 import hashlib
@@ -91,7 +109,7 @@ class MessageList (object):
         out.write('"Content-Transfer-Encoding: 8bit\\n"\n')
         out.write('\n')
         for msg in msgs:
-            out.write(msg.format())
+            out.write(msg.format().encode('utf-8'))
             out.write('\n')
 
 
@@ -105,10 +123,15 @@ class Message (object):
         self._comments = []
         self._preserve = False
 
+    def __repr__(self):
+        if self._empty:
+            return "Empty message"
+        return self.get_string()
+
     class Placeholder (object):
         def __init__ (self, node):
             self.node = node
-            self.name = node.name
+            self.name = unicode(node.name, 'utf-8')
 
     def escape (self, text):
         return text.replace('\\','\\\\').replace('"', "\\\"").replace("\n","\\n").replace("\t","\\t")
@@ -116,6 +139,8 @@ class Message (object):
     def add_text (self, text):
         if len(self._message) == 0 or not(isinstance(self._message[-1], basestring)):
             self._message.append('')
+        if not isinstance(text, unicode):
+            text = unicode(text, 'utf-8')
         self._message[-1] += text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
         if re.sub('\s+', ' ', text).strip() != '':
             self._empty = False
@@ -128,8 +153,8 @@ class Message (object):
     def get_placeholder (self, name):
         placeholder = 1
         for holder in self._placeholders:
-            holdername = '%s-%i' % (holder.name, placeholder)
-            if holdername == name:
+            holdername = u'%s-%i' % (holder.name, placeholder)
+            if holdername == unicode(name, 'utf-8'):
                 return holder
             placeholder += 1
 
@@ -155,7 +180,7 @@ class Message (object):
         if node.children is not None:
             if len(self._message) == 0 or not(isinstance(self._message[-1], basestring)):
                 self._message.append('')
-            self._message[-1] += ('</%s>' % node.name)
+            self._message[-1] += (u'</%s>' % unicode(node.name, 'utf-8'))
 
     def is_empty (self):
         return self._empty
@@ -167,25 +192,28 @@ class Message (object):
         self._ctxt = ctxt
 
     def add_source (self, source):
+        if not isinstance(source, unicode):
+            source = unicode(source, 'utf-8')
         self._sources.append(source)
 
     def get_sources (self):
         return self._sources
 
     def add_comment (self, comment):
-        self._comments.append(comment)
+        if comment is not None:
+            self._comments.append(comment)
 
     def get_comments (self):
         return self._comments
 
     def get_string (self):
-        message = ''
+        message = u''
         placeholder = 1
         for msg in self._message:
             if isinstance(msg, basestring):
                 message += msg
             elif isinstance(msg, Message.Placeholder):
-                message += '<_:%s-%i/>' % (msg.name, placeholder)
+                message += u'<_:%s-%i/>' % (msg.name, placeholder)
                 placeholder += 1
         if not self._preserve:
             message = re.sub('\s+', ' ', message).strip()
@@ -198,38 +226,47 @@ class Message (object):
         self._preserve = preserve
 
     def format (self):
-        ret = ''
+        ret = u''
         for i in range(len(self._comments)):
             if i != 0:
                 ret += '#.\n'
             comment = self._comments[i]
-            while len(comment) > 72:
-                j = comment.rfind(' ', 0, 72)
-                if j == -1:
-                    j = comment.find(' ')
-                if j == -1:
-                    break
-                ret += '#. %s\n' % comment[:j]
-                comment = comment[j+1:]
-            ret += '#. %s\n' % comment
+            if '\n' in comment:
+                doadd = False
+                for line in comment.split('\n'):
+                    if line != '':
+                        doadd = True
+                    if not doadd:
+                        continue
+                    ret += u'#. %s\n' % line
+            else:
+                while len(comment) > 72:
+                    j = comment.rfind(' ', 0, 72)
+                    if j == -1:
+                        j = comment.find(' ')
+                    if j == -1:
+                        break
+                    ret += u'#. %s\n' % comment[:j]
+                    comment = comment[j+1:]
+                ret += '#. %s\n' % comment
         for source in self._sources:
-            ret += '#: %s\n' % source
+            ret += u'#: %s\n' % source
         if self._preserve:
-            ret += '#, no-wrap\n'
+            ret += u'#, no-wrap\n'
         if self._ctxt is not None:
-            ret += 'msgctxt "%s"\n' % self._ctxt
+            ret += u'msgctxt "%s"\n' % self._ctxt
         message = self.get_string()
         if self._preserve:
-            ret += 'msgid ""\n'
+            ret += u'msgid ""\n'
             lines = message.split('\n')
             for line, no in zip(lines, range(len(lines))):
                 if no == len(lines) - 1:
-                    ret += '"%s"\n' % self.escape(line)
+                    ret += u'"%s"\n' % self.escape(line)
                 else:
-                    ret += '"%s\\n"\n' % self.escape(line)
+                    ret += u'"%s\\n"\n' % self.escape(line)
         else:
-            ret += 'msgid "%s"\n' % self.escape(message)
-        ret += 'msgstr ""\n'
+            ret += u'msgid "%s"\n' % self.escape(message)
+        ret += u'msgstr ""\n'
         return ret
 
 
@@ -239,6 +276,12 @@ def xml_child_iter (node):
         yield child
         child = child.next
 
+def xml_attr_iter (node):
+    attr = node.get_properties()
+    while attr is not None:
+        yield attr
+        attr = attr.next
+
 def xml_is_ns_name (node, ns, name):
     if node.type != 'element':
         return False
@@ -267,8 +310,7 @@ class Document (object):
                         hctxt.replaceEntities(1)
                         hctxt.parseDocument()
                         self._localrules.append(hctxt.doc().getRootElement())
-                    else:
-                        self._localrules.append(child)
+                    self._localrules.append(child)
                 pre_process(child)
         pre_process(self._doc)
         self._msgs = messages
@@ -276,6 +318,8 @@ class Document (object):
         self._its_within_text_nodes = {}
         self._its_loc_notes = {}
         self._itst_preserve_space_nodes = {}
+        self._itst_drop_nodes = {}
+        self._itst_contexts = {}
         self._its_lang = {}
         self._itst_lang_attr = {}
         self._itst_credits = None
@@ -286,16 +330,39 @@ class Document (object):
             return
         if xml_is_ns_name(rule, NS_ITS, 'translateRule'):
             if rule.prop('selector') is not None:
-                for node in xpath.xpathEval(rule.prop('selector')):
+                for node in self._try_xpath_eval(xpath, rule.prop('selector')):
                     self._its_translate_nodes[node] = rule.prop('translate')
         elif xml_is_ns_name(rule, NS_ITS, 'withinTextRule'):
             if rule.prop('selector') is not None:
-                for node in xpath.xpathEval(rule.prop('selector')):
+                for node in self._try_xpath_eval(xpath, rule.prop('selector')):
                     self._its_within_text_nodes[node] = rule.prop('withinText')
         elif xml_is_ns_name(rule, NS_ITST, 'preserveSpaceRule'):
             if rule.prop('selector') is not None:
-                for node in xpath.xpathEval(rule.prop('selector')):
+                for node in self._try_xpath_eval(xpath, rule.prop('selector')):
                     self._itst_preserve_space_nodes[node] = rule.prop('preserveSpace')
+        elif xml_is_ns_name(rule, NS_ITST, 'dropRule'):
+            if rule.prop('selector') is not None:
+                for node in self._try_xpath_eval(xpath, rule.prop('selector')):
+                    self._itst_drop_nodes[node] = rule.prop('drop')
+        elif xml_is_ns_name(rule, NS_ITST, 'contextRule'):
+            if rule.prop('selector') is not None:
+                for node in self._try_xpath_eval(xpath, rule.prop('selector')):
+                    if rule.hasProp('context'):
+                        self._itst_contexts[node] = rule.prop('context')
+                    elif rule.hasProp('contextPointer'):
+                        try:
+                            oldnode = xpath.contextNode()
+                        except:
+                            oldnode = None
+                        xpath.setContextNode(node)
+                        ctxt = self._try_xpath_eval(xpath, rule.prop('contextPointer'))
+                        if isinstance(ctxt, basestring):
+                            self._itst_contexts[node] = ctxt
+                        else:
+                            for ctxt in ctxt:
+                                self._itst_contexts[node] = ctxt.content
+                                break
+                        xpath.setContextNode(oldnode)
         elif xml_is_ns_name(rule, NS_ITS, 'locNoteRule'):
             locnote = None
             for child in xml_child_iter(rule):
@@ -306,7 +373,7 @@ class Document (object):
                 if rule.hasProp('locNoteRef'):
                     locnote = 'SEE: ' + re.sub('\s+', ' ', rule.prop('locNoteRef')).strip()
             if rule.prop('selector') is not None:
-                for node in xpath.xpathEval(rule.prop('selector')):
+                for node in self._try_xpath_eval(xpath, rule.prop('selector')):
                     if locnote is not None:
                         self._its_loc_notes[node] = locnote
                     else:
@@ -323,22 +390,29 @@ class Document (object):
                         except:
                             oldnode = None
                         xpath.setContextNode(node)
-                        for note in xpath.xpathEval(sel):
-                            cont = re.sub('\s+', ' ', note.content).strip()
-                            if ref:
-                                cont = 'SEE: ' + cont
-                            self._its_loc_notes[node] = cont
-                            break
+                        note = self._try_xpath_eval(xpath, sel)
+                        if isinstance(note, basestring):
+                            self._its_loc_notes[node] = note
+                        else:
+                            for note in note:
+                                if self.get_preserve_space(note):
+                                    cont = note.content
+                                else:
+                                    cont = re.sub('\s+', ' ', note.content).strip()
+                                if ref:
+                                    cont = 'SEE: ' + cont
+                                self._its_loc_notes[node] = cont
+                                break
                         xpath.setContextNode(oldnode)
         elif xml_is_ns_name(rule, NS_ITS, 'langRule'):
             if rule.prop('selector') is not None and rule.prop('langPointer') is not None:
-                for node in xpath.xpathEval(rule.prop('selector')):
+                for node in self._try_xpath_eval(xpath, rule.prop('selector')):
                     try:
                         oldnode = xpath.contextNode()
                     except:
                         oldnode = None
                     xpath.setContextNode(node)
-                    res = xpath.xpathEval(rule.prop('langPointer'))
+                    res = self._try_xpath_eval(xpath, rule.prop('langPointer'))
                     if len(res) > 0:
                         self._its_lang[node] = res[0].content
                     # We need to construct language attributes, not just read
@@ -350,18 +424,18 @@ class Document (object):
                     xpath.setContextNode(oldnode)
         elif xml_is_ns_name(rule, NS_ITST, 'credits'):
             if rule.prop('appendTo') is not None:
-                for node in xpath.xpathEval(rule.prop('appendTo')):
+                for node in self._try_xpath_eval(xpath, rule.prop('appendTo')):
                     self._itst_credits = (node, rule)
                     break
         elif xml_is_ns_name(rule, NS_ITST, 'externalRefRule'):
             if rule.prop('selector') is not None and rule.prop('refPointer') is not None:
-                for node in xpath.xpathEval(rule.prop('selector')):
+                for node in self._try_xpath_eval(xpath, rule.prop('selector')):
                     try:
                         oldnode = xpath.contextNode()
                     except:
                         oldnode = None
                     xpath.setContextNode(node)
-                    res = xpath.xpathEval(rule.prop('refPointer'))
+                    res = self._try_xpath_eval(xpath, rule.prop('refPointer'))
                     if len(res) > 0:
                         self._itst_externals.append((node, res[0].content))
                     xpath.setContextNode(oldnode)
@@ -374,7 +448,9 @@ class Document (object):
         dirs.append(ddir)
         ddir = os.getenv('XDG_DATA_DIRS', '')
         if ddir == '':
-            ddir = '/usr/local/share:/usr/share'
+            if DATADIR not in ('/usr/local/share', '/usr/share'):
+                ddir += DATADIR + ':'
+            ddir += '/usr/local/share:/usr/share'
         dirs.extend(ddir.split(':'))
         ddone = {}
         for ddir in dirs:
@@ -410,7 +486,7 @@ class Document (object):
                         nsdef = nsdef.next
                     par = par.parent
                 if match.hasProp('selector'):
-                    if len(xpath.xpathEval(match.prop('selector'))) > 0:
+                    if len(self._try_xpath_eval(xpath, match.prop('selector'))) > 0:
                         matched = True
                         break
         if matched == False:
@@ -443,6 +519,8 @@ class Document (object):
             xpath = self._doc.xpathNewContext()
             reg_ns(xpath, rules)
             for rule in xml_child_iter(rules):
+                if rule.type != 'element':
+                    continue
                 if rule.nsDefs() is not None:
                     rule_xpath = self._doc.xpathNewContent()
                     reg_ns(rule_xpath, rule)
@@ -503,16 +581,27 @@ class Document (object):
             node = self._doc.getRootElement()
         if node is None or node.type != 'element':
             return
+        if ((node.hasNsProp('drop', NS_ITST) and node.nsProp('drop', NS_ITST) == 'yes') or
+            self._itst_drop_nodes.get(node, 'no') == 'yes'):
+            prev = node.prev
+            node.unlinkNode()
+            node.freeNode()
+            if prev.isBlankNode():
+                prev.unlinkNode()
+                prev.freeNode()
+            return
         if is_root:
             self.merge_credits(translations, language, node)
         msg = self._msgs.get_message_by_node(node)
         if msg is None:
+            self.translate_attrs(node, node)
             children = [child for child in xml_child_iter(node)]
             for child in children:
                 self.merge_translations(translations, language, node=child)
         else:
             newnode = self.get_translated(node, translations)
             if newnode != node:
+                self.translate_attrs(node, newnode)
                 node.replaceNode(newnode)
         if is_root:
             # Apply language attributes to untranslated nodes. We don't do
@@ -556,11 +645,24 @@ class Document (object):
                         fix_node_ns(child, childnsdefs)
             fix_node_ns(node, {})
 
+    def translate_attrs(self, oldnode, newnode):
+        trans_attrs = [attr for attr in xml_attr_iter(oldnode) if self._its_translate_nodes.get(attr, 'no') == 'yes']
+        for attr in trans_attrs:
+            newcontent = translations.ugettext(attr.get_content())
+            if newcontent:
+                newnode.setProp(attr.name, translations.ugettext(attr.get_content()))
+
     def get_translated (self, node, translations):
         msg = self._msgs.get_message_by_node(node)
         if msg is None:
             return node
-        trans = translations.ugettext(msg.get_string())
+        msgstr = msg.get_string()
+        # Dear Python, please implement pgettext.
+        # http://bugs.python.org/issue2504
+        # Sincerely, Shaun
+        if msg.get_context() is not None:
+            msgstr = msg.get_context() + '\x04' + msgstr
+        trans = translations.ugettext(msgstr)
         if trans is None:
             return node
         nss = {}
@@ -585,7 +687,8 @@ class Document (object):
         ctxt.parseDocument()
         trnode = ctxt.doc().getRootElement()
         def scan_node(node):
-            for child in xml_child_iter(node):
+            children = [child for child in xml_child_iter(node)]
+            for child in children:
                 if child.type != 'element':
                     continue
                 if child.ns() is not None and child.ns().content == NS_BLANK:
@@ -632,12 +735,17 @@ class Document (object):
             return
         if node.type != 'element':
             return
+        if node.hasNsProp('drop', NS_ITST) and node.nsProp('drop', NS_ITST) == 'yes':
+            return
+        if self._itst_drop_nodes.get(node, 'no') == 'yes':
+            return
         translate = self.get_its_translate(node)
         if translate is None:
             if self._in_translatable:
                 translate = 'yes'
             else:
                 translate = 'no'
+        withinText = False
         if translate == 'no':
             if msg is not None:
                 msg.add_placeholder(node)
@@ -649,16 +757,35 @@ class Document (object):
                 if msg is not None:
                     msg.add_placeholder(node)
                 msg = Message()
+                ctxt = None
+                if node.hasNsProp('context', NS_ITST):
+                    ctxt = node.nsProp('context', NS_ITST)
+                if ctxt is None:
+                    ctxt = self._itst_contexts.get(node)
+                if ctxt is not None:
+                    msg.set_context(ctxt)
                 if self.get_preserve_space(node):
                     msg.set_preserve_space()
                 msg.add_source('%s:%i(%s/%s)' % (self._doc.name, node.lineNo(), node.parent.name, node.name))
             else:
+                withinText = True
                 msg.add_start_tag(node)
 
+        if not withinText:
+            # Add msg for translatable node attributes
+            for attr in xml_attr_iter(node):
+                if self._its_translate_nodes.get(attr, 'no') == 'yes':
+                    attr_msg = Message()
+                    attr_msg.add_source('%s:%i(%s/%s@%s)' % (
+                        self._doc.name, node.lineNo(), node.parent.name, node.name, attr.name))
+                    attr_msg.add_text(attr.content)
+                    if comments:
+                        attr_msg.add_comment(self.get_its_loc_note(attr))
+                    self._msgs.add_message(attr_msg, attr)
+
+
         if comments and msg is not None:
-            comment = self.get_its_loc_note(node)
-            if comment is not None:
-                msg.add_comment(comment)
+            msg.add_comment(self.get_its_loc_note(node))
 
         in_translatable = self._in_translatable
         self._in_translatable = (translate == 'yes')
@@ -710,6 +837,14 @@ class Document (object):
                 return 'SEE: ' + re.sub('\s+', ' ', node.prop('locNoteRef')).strip()
         return self._its_loc_notes.get(node, None)
 
+    @staticmethod
+    def _try_xpath_eval (xpath, expr):
+        try:
+            return xpath.xpathEval(expr)
+        except:
+            sys.stderr.write('Warning: Invalid XPath: %s\n' % expr)
+            return []
+
 
 _locale_pattern = re.compile('([a-zA-Z0-9-]+)(_[A-Za-z0-9]+)?(@[A-Za-z0-9]+)?(\.[A-Za-z0-9]+)?')
 def convert_locale (locale):
@@ -768,7 +903,7 @@ if __name__ == '__main__':
     (opts, args) = options.parse_args(sys.argv)
 
     if opts.version:
-        print 'itstool ' + VERSION
+        print('itstool %s' % VERSION)
         sys.exit(0)
 
     if opts.merge is None:
diff --git a/itstool.1 b/itstool.1
new file mode 100644 (file)
index 0000000..7e8f1de
--- /dev/null
+++ b/itstool.1
@@ -0,0 +1,77 @@
+.TH ITSTOOL "1" "May 2011" "itstool 1.1.0"
+.SH NAME
+itstool \- convert between XML and PO using ITS
+
+.SH SYNOPSIS
+itstool [OPTIONS] [XMLFILES]
+.br
+itstool \fB\-m\fR <MOFILE> [OPTIONS] [XMLFILES]
+
+.SH DESCRIPTION
+.BR itstool
+extracts messages from XML files and outputs PO template files, then merges
+translations from MO files to create translated XML files. It determines what
+to translate and how to chunk it into messages using the W3C Internationalization
+Tag Set (ITS).
+
+To extract messages from XML files \fBFILES\fR and output them to \fBOUT.pot\fR:
+
+\fBitstool -o OUT.pot FILES\fR
+
+After merging with existing translations or translating strings, generate an
+MO file with \fBmsgfmt(1)\fR, then output translated files to the directory
+\fBDIR\fR:
+
+\fBitstool -m OUT.mo -o DIR FILES\fR
+
+ITS definitions are loaded from the built-in rules, rules embedded in the source
+XML files, files passed with the \fB-i\fR option, and ITS attributes in the source
+XML files. Later definitions take precedence.
+
+.SH OPTIONS
+.SS "Extracting"
+.IP "\fB\-o \fIFILE\fR" 4
+.PD 0
+.IP "\fB\-\-out=\fIFILE \fR" 4
+.PD
+output PO template to the file
+.BR OUT
+.SS "Merging"
+.IP "\fB\-m \fIFILE\fR" 4
+.PD 0
+.IP "\fB\-\-merge=\fIFILE \fR" 4
+.PD
+merge from an MO file
+.BR FILE
+and output XML files
+.TP
+.IP "\fB\-l \fILANG\fR" 4
+.PD 0
+.IP "\fB\-\-lang=\fILANG \fR" 4
+.PD
+explicitly set the language code output to XML
+.TP
+.IP "\fB\-o \fIFILE\fR" 4
+.PD 0
+.IP "\fB\-\-out=\fIFILE \fR" 4
+.PD
+output XML files in the directory
+.BR OUT
+.SS "Common"
+.IP "\fB\-i \fIITS\fR" 4
+.PD 0
+.IP "\fB\-\-its=\fIITS \fR" 4
+.PD
+load the ITS rules in the file ITS (can specify
+multiple times)
+
+.SH AUTHOR
+Shaun McCance <shaunm@gnome.org>
+
+.SH "SEE ALSO"
+More documentation for
+.B itstool
+is maintained online. For more information, see:
+.IP
+.B http://itstool.org/documentation/
+.PP
diff --git a/itstool.1.in b/itstool.1.in
new file mode 100644 (file)
index 0000000..38333ca
--- /dev/null
@@ -0,0 +1,77 @@
+.TH ITSTOOL "1" "May 2011" "itstool @VERSION@"
+.SH NAME
+itstool \- convert between XML and PO using ITS
+
+.SH SYNOPSIS
+itstool [OPTIONS] [XMLFILES]
+.br
+itstool \fB\-m\fR <MOFILE> [OPTIONS] [XMLFILES]
+
+.SH DESCRIPTION
+.BR itstool
+extracts messages from XML files and outputs PO template files, then merges
+translations from MO files to create translated XML files. It determines what
+to translate and how to chunk it into messages using the W3C Internationalization
+Tag Set (ITS).
+
+To extract messages from XML files \fBFILES\fR and output them to \fBOUT.pot\fR:
+
+\fBitstool -o OUT.pot FILES\fR
+
+After merging with existing translations or translating strings, generate an
+MO file with \fBmsgfmt(1)\fR, then output translated files to the directory
+\fBDIR\fR:
+
+\fBitstool -m OUT.mo -o DIR FILES\fR
+
+ITS definitions are loaded from the built-in rules, rules embedded in the source
+XML files, files passed with the \fB-i\fR option, and ITS attributes in the source
+XML files. Later definitions take precedence.
+
+.SH OPTIONS
+.SS "Extracting"
+.IP "\fB\-o \fIFILE\fR" 4
+.PD 0
+.IP "\fB\-\-out=\fIFILE \fR" 4
+.PD
+output PO template to the file
+.BR OUT
+.SS "Merging"
+.IP "\fB\-m \fIFILE\fR" 4
+.PD 0
+.IP "\fB\-\-merge=\fIFILE \fR" 4
+.PD
+merge from an MO file
+.BR FILE
+and output XML files
+.TP
+.IP "\fB\-l \fILANG\fR" 4
+.PD 0
+.IP "\fB\-\-lang=\fILANG \fR" 4
+.PD
+explicitly set the language code output to XML
+.TP
+.IP "\fB\-o \fIFILE\fR" 4
+.PD 0
+.IP "\fB\-\-out=\fIFILE \fR" 4
+.PD
+output XML files in the directory
+.BR OUT
+.SS "Common"
+.IP "\fB\-i \fIITS\fR" 4
+.PD 0
+.IP "\fB\-\-its=\fIITS \fR" 4
+.PD
+load the ITS rules in the file ITS (can specify
+multiple times)
+
+.SH AUTHOR
+Shaun McCance <shaunm@gnome.org>
+
+.SH "SEE ALSO"
+More documentation for
+.B itstool
+is maintained online. For more information, see:
+.IP
+.B http://itstool.org/documentation/
+.PP
index 445628e..2401918 100755 (executable)
@@ -1,6 +1,24 @@
-#!/usr/bin/env python
+#!/usr/bin/python -s
+#
+# Copyright (c) 2010-2011 Shaun McCance <shaunm@gnome.org>
+#
+# ITS Tool program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# ITS Tool is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with ITS Tool; if not, write to the Free Software Foundation, 59 Temple
+# Place, Suite 330, Boston, MA  0211-1307  USA.
+#
 
 VERSION="@VERSION@"
+DATADIR="@DATADIR@"
 
 import gettext
 import hashlib
@@ -91,7 +109,7 @@ class MessageList (object):
         out.write('"Content-Transfer-Encoding: 8bit\\n"\n')
         out.write('\n')
         for msg in msgs:
-            out.write(msg.format())
+            out.write(msg.format().encode('utf-8'))
             out.write('\n')
 
 
@@ -105,10 +123,15 @@ class Message (object):
         self._comments = []
         self._preserve = False
 
+    def __repr__(self):
+        if self._empty:
+            return "Empty message"
+        return self.get_string()
+
     class Placeholder (object):
         def __init__ (self, node):
             self.node = node
-            self.name = node.name
+            self.name = unicode(node.name, 'utf-8')
 
     def escape (self, text):
         return text.replace('\\','\\\\').replace('"', "\\\"").replace("\n","\\n").replace("\t","\\t")
@@ -116,6 +139,8 @@ class Message (object):
     def add_text (self, text):
         if len(self._message) == 0 or not(isinstance(self._message[-1], basestring)):
             self._message.append('')
+        if not isinstance(text, unicode):
+            text = unicode(text, 'utf-8')
         self._message[-1] += text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
         if re.sub('\s+', ' ', text).strip() != '':
             self._empty = False
@@ -128,8 +153,8 @@ class Message (object):
     def get_placeholder (self, name):
         placeholder = 1
         for holder in self._placeholders:
-            holdername = '%s-%i' % (holder.name, placeholder)
-            if holdername == name:
+            holdername = u'%s-%i' % (holder.name, placeholder)
+            if holdername == unicode(name, 'utf-8'):
                 return holder
             placeholder += 1
 
@@ -155,7 +180,7 @@ class Message (object):
         if node.children is not None:
             if len(self._message) == 0 or not(isinstance(self._message[-1], basestring)):
                 self._message.append('')
-            self._message[-1] += ('</%s>' % node.name)
+            self._message[-1] += (u'</%s>' % unicode(node.name, 'utf-8'))
 
     def is_empty (self):
         return self._empty
@@ -167,25 +192,28 @@ class Message (object):
         self._ctxt = ctxt
 
     def add_source (self, source):
+        if not isinstance(source, unicode):
+            source = unicode(source, 'utf-8')
         self._sources.append(source)
 
     def get_sources (self):
         return self._sources
 
     def add_comment (self, comment):
-        self._comments.append(comment)
+        if comment is not None:
+            self._comments.append(comment)
 
     def get_comments (self):
         return self._comments
 
     def get_string (self):
-        message = ''
+        message = u''
         placeholder = 1
         for msg in self._message:
             if isinstance(msg, basestring):
                 message += msg
             elif isinstance(msg, Message.Placeholder):
-                message += '<_:%s-%i/>' % (msg.name, placeholder)
+                message += u'<_:%s-%i/>' % (msg.name, placeholder)
                 placeholder += 1
         if not self._preserve:
             message = re.sub('\s+', ' ', message).strip()
@@ -198,38 +226,47 @@ class Message (object):
         self._preserve = preserve
 
     def format (self):
-        ret = ''
+        ret = u''
         for i in range(len(self._comments)):
             if i != 0:
                 ret += '#.\n'
             comment = self._comments[i]
-            while len(comment) > 72:
-                j = comment.rfind(' ', 0, 72)
-                if j == -1:
-                    j = comment.find(' ')
-                if j == -1:
-                    break
-                ret += '#. %s\n' % comment[:j]
-                comment = comment[j+1:]
-            ret += '#. %s\n' % comment
+            if '\n' in comment:
+                doadd = False
+                for line in comment.split('\n'):
+                    if line != '':
+                        doadd = True
+                    if not doadd:
+                        continue
+                    ret += u'#. %s\n' % line
+            else:
+                while len(comment) > 72:
+                    j = comment.rfind(' ', 0, 72)
+                    if j == -1:
+                        j = comment.find(' ')
+                    if j == -1:
+                        break
+                    ret += u'#. %s\n' % comment[:j]
+                    comment = comment[j+1:]
+                ret += '#. %s\n' % comment
         for source in self._sources:
-            ret += '#: %s\n' % source
+            ret += u'#: %s\n' % source
         if self._preserve:
-            ret += '#, no-wrap\n'
+            ret += u'#, no-wrap\n'
         if self._ctxt is not None:
-            ret += 'msgctxt "%s"\n' % self._ctxt
+            ret += u'msgctxt "%s"\n' % self._ctxt
         message = self.get_string()
         if self._preserve:
-            ret += 'msgid ""\n'
+            ret += u'msgid ""\n'
             lines = message.split('\n')
             for line, no in zip(lines, range(len(lines))):
                 if no == len(lines) - 1:
-                    ret += '"%s"\n' % self.escape(line)
+                    ret += u'"%s"\n' % self.escape(line)
                 else:
-                    ret += '"%s\\n"\n' % self.escape(line)
+                    ret += u'"%s\\n"\n' % self.escape(line)
         else:
-            ret += 'msgid "%s"\n' % self.escape(message)
-        ret += 'msgstr ""\n'
+            ret += u'msgid "%s"\n' % self.escape(message)
+        ret += u'msgstr ""\n'
         return ret
 
 
@@ -239,6 +276,12 @@ def xml_child_iter (node):
         yield child
         child = child.next
 
+def xml_attr_iter (node):
+    attr = node.get_properties()
+    while attr is not None:
+        yield attr
+        attr = attr.next
+
 def xml_is_ns_name (node, ns, name):
     if node.type != 'element':
         return False
@@ -267,8 +310,7 @@ class Document (object):
                         hctxt.replaceEntities(1)
                         hctxt.parseDocument()
                         self._localrules.append(hctxt.doc().getRootElement())
-                    else:
-                        self._localrules.append(child)
+                    self._localrules.append(child)
                 pre_process(child)
         pre_process(self._doc)
         self._msgs = messages
@@ -276,6 +318,8 @@ class Document (object):
         self._its_within_text_nodes = {}
         self._its_loc_notes = {}
         self._itst_preserve_space_nodes = {}
+        self._itst_drop_nodes = {}
+        self._itst_contexts = {}
         self._its_lang = {}
         self._itst_lang_attr = {}
         self._itst_credits = None
@@ -286,16 +330,39 @@ class Document (object):
             return
         if xml_is_ns_name(rule, NS_ITS, 'translateRule'):
             if rule.prop('selector') is not None:
-                for node in xpath.xpathEval(rule.prop('selector')):
+                for node in self._try_xpath_eval(xpath, rule.prop('selector')):
                     self._its_translate_nodes[node] = rule.prop('translate')
         elif xml_is_ns_name(rule, NS_ITS, 'withinTextRule'):
             if rule.prop('selector') is not None:
-                for node in xpath.xpathEval(rule.prop('selector')):
+                for node in self._try_xpath_eval(xpath, rule.prop('selector')):
                     self._its_within_text_nodes[node] = rule.prop('withinText')
         elif xml_is_ns_name(rule, NS_ITST, 'preserveSpaceRule'):
             if rule.prop('selector') is not None:
-                for node in xpath.xpathEval(rule.prop('selector')):
+                for node in self._try_xpath_eval(xpath, rule.prop('selector')):
                     self._itst_preserve_space_nodes[node] = rule.prop('preserveSpace')
+        elif xml_is_ns_name(rule, NS_ITST, 'dropRule'):
+            if rule.prop('selector') is not None:
+                for node in self._try_xpath_eval(xpath, rule.prop('selector')):
+                    self._itst_drop_nodes[node] = rule.prop('drop')
+        elif xml_is_ns_name(rule, NS_ITST, 'contextRule'):
+            if rule.prop('selector') is not None:
+                for node in self._try_xpath_eval(xpath, rule.prop('selector')):
+                    if rule.hasProp('context'):
+                        self._itst_contexts[node] = rule.prop('context')
+                    elif rule.hasProp('contextPointer'):
+                        try:
+                            oldnode = xpath.contextNode()
+                        except:
+                            oldnode = None
+                        xpath.setContextNode(node)
+                        ctxt = self._try_xpath_eval(xpath, rule.prop('contextPointer'))
+                        if isinstance(ctxt, basestring):
+                            self._itst_contexts[node] = ctxt
+                        else:
+                            for ctxt in ctxt:
+                                self._itst_contexts[node] = ctxt.content
+                                break
+                        xpath.setContextNode(oldnode)
         elif xml_is_ns_name(rule, NS_ITS, 'locNoteRule'):
             locnote = None
             for child in xml_child_iter(rule):
@@ -306,7 +373,7 @@ class Document (object):
                 if rule.hasProp('locNoteRef'):
                     locnote = 'SEE: ' + re.sub('\s+', ' ', rule.prop('locNoteRef')).strip()
             if rule.prop('selector') is not None:
-                for node in xpath.xpathEval(rule.prop('selector')):
+                for node in self._try_xpath_eval(xpath, rule.prop('selector')):
                     if locnote is not None:
                         self._its_loc_notes[node] = locnote
                     else:
@@ -323,22 +390,29 @@ class Document (object):
                         except:
                             oldnode = None
                         xpath.setContextNode(node)
-                        for note in xpath.xpathEval(sel):
-                            cont = re.sub('\s+', ' ', note.content).strip()
-                            if ref:
-                                cont = 'SEE: ' + cont
-                            self._its_loc_notes[node] = cont
-                            break
+                        note = self._try_xpath_eval(xpath, sel)
+                        if isinstance(note, basestring):
+                            self._its_loc_notes[node] = note
+                        else:
+                            for note in note:
+                                if self.get_preserve_space(note):
+                                    cont = note.content
+                                else:
+                                    cont = re.sub('\s+', ' ', note.content).strip()
+                                if ref:
+                                    cont = 'SEE: ' + cont
+                                self._its_loc_notes[node] = cont
+                                break
                         xpath.setContextNode(oldnode)
         elif xml_is_ns_name(rule, NS_ITS, 'langRule'):
             if rule.prop('selector') is not None and rule.prop('langPointer') is not None:
-                for node in xpath.xpathEval(rule.prop('selector')):
+                for node in self._try_xpath_eval(xpath, rule.prop('selector')):
                     try:
                         oldnode = xpath.contextNode()
                     except:
                         oldnode = None
                     xpath.setContextNode(node)
-                    res = xpath.xpathEval(rule.prop('langPointer'))
+                    res = self._try_xpath_eval(xpath, rule.prop('langPointer'))
                     if len(res) > 0:
                         self._its_lang[node] = res[0].content
                     # We need to construct language attributes, not just read
@@ -350,18 +424,18 @@ class Document (object):
                     xpath.setContextNode(oldnode)
         elif xml_is_ns_name(rule, NS_ITST, 'credits'):
             if rule.prop('appendTo') is not None:
-                for node in xpath.xpathEval(rule.prop('appendTo')):
+                for node in self._try_xpath_eval(xpath, rule.prop('appendTo')):
                     self._itst_credits = (node, rule)
                     break
         elif xml_is_ns_name(rule, NS_ITST, 'externalRefRule'):
             if rule.prop('selector') is not None and rule.prop('refPointer') is not None:
-                for node in xpath.xpathEval(rule.prop('selector')):
+                for node in self._try_xpath_eval(xpath, rule.prop('selector')):
                     try:
                         oldnode = xpath.contextNode()
                     except:
                         oldnode = None
                     xpath.setContextNode(node)
-                    res = xpath.xpathEval(rule.prop('refPointer'))
+                    res = self._try_xpath_eval(xpath, rule.prop('refPointer'))
                     if len(res) > 0:
                         self._itst_externals.append((node, res[0].content))
                     xpath.setContextNode(oldnode)
@@ -374,7 +448,9 @@ class Document (object):
         dirs.append(ddir)
         ddir = os.getenv('XDG_DATA_DIRS', '')
         if ddir == '':
-            ddir = '/usr/local/share:/usr/share'
+            if DATADIR not in ('/usr/local/share', '/usr/share'):
+                ddir += DATADIR + ':'
+            ddir += '/usr/local/share:/usr/share'
         dirs.extend(ddir.split(':'))
         ddone = {}
         for ddir in dirs:
@@ -410,7 +486,7 @@ class Document (object):
                         nsdef = nsdef.next
                     par = par.parent
                 if match.hasProp('selector'):
-                    if len(xpath.xpathEval(match.prop('selector'))) > 0:
+                    if len(self._try_xpath_eval(xpath, match.prop('selector'))) > 0:
                         matched = True
                         break
         if matched == False:
@@ -443,6 +519,8 @@ class Document (object):
             xpath = self._doc.xpathNewContext()
             reg_ns(xpath, rules)
             for rule in xml_child_iter(rules):
+                if rule.type != 'element':
+                    continue
                 if rule.nsDefs() is not None:
                     rule_xpath = self._doc.xpathNewContent()
                     reg_ns(rule_xpath, rule)
@@ -503,16 +581,27 @@ class Document (object):
             node = self._doc.getRootElement()
         if node is None or node.type != 'element':
             return
+        if ((node.hasNsProp('drop', NS_ITST) and node.nsProp('drop', NS_ITST) == 'yes') or
+            self._itst_drop_nodes.get(node, 'no') == 'yes'):
+            prev = node.prev
+            node.unlinkNode()
+            node.freeNode()
+            if prev.isBlankNode():
+                prev.unlinkNode()
+                prev.freeNode()
+            return
         if is_root:
             self.merge_credits(translations, language, node)
         msg = self._msgs.get_message_by_node(node)
         if msg is None:
+            self.translate_attrs(node, node)
             children = [child for child in xml_child_iter(node)]
             for child in children:
                 self.merge_translations(translations, language, node=child)
         else:
             newnode = self.get_translated(node, translations)
             if newnode != node:
+                self.translate_attrs(node, newnode)
                 node.replaceNode(newnode)
         if is_root:
             # Apply language attributes to untranslated nodes. We don't do
@@ -556,11 +645,24 @@ class Document (object):
                         fix_node_ns(child, childnsdefs)
             fix_node_ns(node, {})
 
+    def translate_attrs(self, oldnode, newnode):
+        trans_attrs = [attr for attr in xml_attr_iter(oldnode) if self._its_translate_nodes.get(attr, 'no') == 'yes']
+        for attr in trans_attrs:
+            newcontent = translations.ugettext(attr.get_content())
+            if newcontent:
+                newnode.setProp(attr.name, translations.ugettext(attr.get_content()))
+
     def get_translated (self, node, translations):
         msg = self._msgs.get_message_by_node(node)
         if msg is None:
             return node
-        trans = translations.ugettext(msg.get_string())
+        msgstr = msg.get_string()
+        # Dear Python, please implement pgettext.
+        # http://bugs.python.org/issue2504
+        # Sincerely, Shaun
+        if msg.get_context() is not None:
+            msgstr = msg.get_context() + '\x04' + msgstr
+        trans = translations.ugettext(msgstr)
         if trans is None:
             return node
         nss = {}
@@ -585,7 +687,8 @@ class Document (object):
         ctxt.parseDocument()
         trnode = ctxt.doc().getRootElement()
         def scan_node(node):
-            for child in xml_child_iter(node):
+            children = [child for child in xml_child_iter(node)]
+            for child in children:
                 if child.type != 'element':
                     continue
                 if child.ns() is not None and child.ns().content == NS_BLANK:
@@ -632,12 +735,17 @@ class Document (object):
             return
         if node.type != 'element':
             return
+        if node.hasNsProp('drop', NS_ITST) and node.nsProp('drop', NS_ITST) == 'yes':
+            return
+        if self._itst_drop_nodes.get(node, 'no') == 'yes':
+            return
         translate = self.get_its_translate(node)
         if translate is None:
             if self._in_translatable:
                 translate = 'yes'
             else:
                 translate = 'no'
+        withinText = False
         if translate == 'no':
             if msg is not None:
                 msg.add_placeholder(node)
@@ -649,16 +757,35 @@ class Document (object):
                 if msg is not None:
                     msg.add_placeholder(node)
                 msg = Message()
+                ctxt = None
+                if node.hasNsProp('context', NS_ITST):
+                    ctxt = node.nsProp('context', NS_ITST)
+                if ctxt is None:
+                    ctxt = self._itst_contexts.get(node)
+                if ctxt is not None:
+                    msg.set_context(ctxt)
                 if self.get_preserve_space(node):
                     msg.set_preserve_space()
                 msg.add_source('%s:%i(%s/%s)' % (self._doc.name, node.lineNo(), node.parent.name, node.name))
             else:
+                withinText = True
                 msg.add_start_tag(node)
 
+        if not withinText:
+            # Add msg for translatable node attributes
+            for attr in xml_attr_iter(node):
+                if self._its_translate_nodes.get(attr, 'no') == 'yes':
+                    attr_msg = Message()
+                    attr_msg.add_source('%s:%i(%s/%s@%s)' % (
+                        self._doc.name, node.lineNo(), node.parent.name, node.name, attr.name))
+                    attr_msg.add_text(attr.content)
+                    if comments:
+                        attr_msg.add_comment(self.get_its_loc_note(attr))
+                    self._msgs.add_message(attr_msg, attr)
+
+
         if comments and msg is not None:
-            comment = self.get_its_loc_note(node)
-            if comment is not None:
-                msg.add_comment(comment)
+            msg.add_comment(self.get_its_loc_note(node))
 
         in_translatable = self._in_translatable
         self._in_translatable = (translate == 'yes')
@@ -710,6 +837,14 @@ class Document (object):
                 return 'SEE: ' + re.sub('\s+', ' ', node.prop('locNoteRef')).strip()
         return self._its_loc_notes.get(node, None)
 
+    @staticmethod
+    def _try_xpath_eval (xpath, expr):
+        try:
+            return xpath.xpathEval(expr)
+        except:
+            sys.stderr.write('Warning: Invalid XPath: %s\n' % expr)
+            return []
+
 
 _locale_pattern = re.compile('([a-zA-Z0-9-]+)(_[A-Za-z0-9]+)?(@[A-Za-z0-9]+)?(\.[A-Za-z0-9]+)?')
 def convert_locale (locale):
@@ -768,7 +903,7 @@ if __name__ == '__main__':
     (opts, args) = options.parse_args(sys.argv)
 
     if opts.version:
-        print 'itstool ' + VERSION
+        print('itstool %s' % VERSION)
         sys.exit(0)
 
     if opts.merge is None: