From 3488a6c1c4deb1eeaf30b6c65d50c4c39f92182d Mon Sep 17 00:00:00 2001 From: Remo Ronca Date: Mon, 4 May 2015 16:51:50 -0600 Subject: [PATCH] Added support for "acronym plurals", e.g., now an acronym defined as MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit >>> \newacro{RFC}[RFC]{Request for Comment} can be use in a text as >>> See RFC 1234 for more information. Multiple RFCs are covering security protocols. resulting in >>> See \ac{RFC} for more information. Multiple \acp{RFC} are covering security protocols In order to achieve this, find_abbreviations() [in writer.c] had to "partially" match an abbreviation and pass a detected plural along the corresponding node->key. Thus, a new node key type ABBRPLURAL has been introduced [in libMultiMarkdown.h] and propagated in all print_*_node() functions. As the partial abbreviation lookup necessitated frequent calls of strlen() performance optimization dictated to extend the node structure by adding length element instead. Visible repercussions are: a strdup() replacement strdup_ext(), modifications of the trim_*() functions, and alteration of all node creations and node copies. ================================================================================================================ Added support for the LaTeX glos­saries pack­age by Ni­cola Tal­bot (see http://www.ctan.org/pkg/glossaries). For serious documents this package allows a better acronym and glossary management. Due to its preferred workflow which requires a separate makeidx call, it seems not possible to circumvent the introduced code changes by using TeX's "\renewcommand" for MMD's supported "\ac" and "\newacro" commands. By specifying the metadata key >>> LaTeX Acronym Package: [acronym | glossaries ] the preferred acronym package can be selected. Omitting the "LaTeX Acronym Package" key selects, of course, MMD's default behavior, i.e., "acronym package" support. For this feature the METAKEY, ABBREVIATION, and ABBR switch-cases in print_latex_node() [in latex.c] and the scratch_pad struct [in parser.h] have been modified. An additional enum (latex_acronym_package) has been introduced. The additional scratch_pad element latex_acronym_config is stored in a compact way. ================================================================================================================ Added support for configuring an acronym's label creation MMD current preference in creating labels is to avoid duplicate acronym keys by concatenating the key with the acronym's text. This makes manual referencing of acronym labels very difficult. Unfortunately, manual acronym referencing in unavoidable, when longer documents are written, e.g., intentionally selecting the full acronym text after its first mentioning, using acronyms in "HTML comment escaped" LaTeX blocks like abstracts, etc. By specifying the metadata key >>> LaTeX Acronym Label Style: [Abbreviation | Full] the preferred acronym label creation style can be selected. Omitting the "LaTeX Acronym Label style" key selects, of course, MMD's default behavior, i.e., "full" label creation. For this feature the METAKEY, ABBREVIATION, and ABBR switch-cases in print_latex_node() [in latex.c] and the scratch_pad struct [in parser.h] have been modified. An additional enum (latex_acronym_label_style) has been introduced. The additional scratch_pad element latex_acronym_config is stored in a compact way. --- html.c | 9 +-- latex.c | 83 ++++++++++++++++++++------ libMultiMarkdown.h | 14 +++++ lyx.c | 3 +- memoir.c | 2 +- odf.c | 3 +- opml.c | 2 +- parse_utilities.c | 144 ++++++++++++++++++++++++++++++++------------- parser.h | 28 ++++++++- rtf.c | 2 +- writer.c | 18 ++++-- 11 files changed, 234 insertions(+), 74 deletions(-) diff --git a/html.c b/html.c index c3cc70a..6de8ed8 100644 --- a/html.c +++ b/html.c @@ -71,6 +71,7 @@ void print_html_node(GString *out, node *n, scratch_pad *scratch) { /* fprintf(stderr, "str: '%s'\n", n->str); */ print_html_string(out,n->str, scratch); break; + case ABBRPLURAL: case ABBR: if (strlen(n->children->str) == 0) { g_string_append_printf(out, ""); @@ -139,7 +140,7 @@ void print_html_node(GString *out, node *n, scratch_pad *scratch) { case VERBATIMFENCE: pad(out, 2, scratch); if ((n->children != NULL) && (n->children->key == VERBATIMTYPE)) { - trim_trailing_whitespace(n->children->str); + trim_trailing_whitespace_in_node_str(n->children); if (strlen(n->children->str) > 0) g_string_append_printf(out, "
", n->children->str);
 				else
@@ -244,11 +245,11 @@ void print_html_node(GString *out, node *n, scratch_pad *scratch) {
 				print_html_node(out, n->children, scratch);
 				g_string_append_printf(out, "\"/>\n");
 			} else if (strcmp(n->str, "xhtmlheader") == 0) {
-				trim_trailing_whitespace(n->children->str);
+				trim_trailing_whitespace_in_node_str(n->children);
 				print_raw_node(out, n->children);
 				g_string_append_printf(out, "\n");
 			} else if (strcmp(n->str, "htmlheader") == 0) {
-				trim_trailing_whitespace(n->children->str);
+				trim_trailing_whitespace_in_node_str(n->children);
 				print_raw_node(out, n->children);
 				g_string_append_printf(out, "\n");
 			} else if (strcmp(n->str, "mmdfooter") == 0) {
@@ -261,7 +262,7 @@ void print_html_node(GString *out, node *n, scratch_pad *scratch) {
 			}
 			break;
 		case METAVALUE:
-			trim_trailing_whitespace(n->str);
+			trim_trailing_whitespace_in_node_str(n);
 			print_html_string(out,n->str, scratch);
 			break;
 		case FOOTER:
diff --git a/latex.c b/latex.c
index a9b2bc2..2e40fc2 100644
--- a/latex.c
+++ b/latex.c
@@ -99,16 +99,33 @@ void print_latex_node(GString *out, node *n, scratch_pad *scratch) {
 		case ABBREVIATION:
 			/* We combine the short and full names, since stripping non-ascii characters may result
 				in a conflict otherwise.  This at least makes it less likely. */
-			width = ascii_label_from_node(n->children);
-			temp = ascii_label_from_string(n->str);
-			g_string_append_printf(out, "\\newacro{%s%s}[",width,temp);
-			print_latex_node_tree(out, n->children, scratch);
-			g_string_append_printf(out, "]{");
-			trim_trailing_whitespace(n->str);
+			temp_str = g_string_new("");
+			
+			if(LATEX_ACRONYM_LABEL_STYLE_ABBR == (scratch->latex_acronym_config&LATEX_ACRONYM_LABEL_STYLE_MASK)) {
+				print_raw_node(temp_str, n->children);
+			} else {
+				g_string_append(temp_str, ascii_label_from_node(n->children));
+				g_string_append(temp_str, ascii_label_from_string(n->str));
+			}
+			
+			switch(scratch->latex_acronym_config&LATEX_ACRONYM_PACKAGE_MASK) {
+				case LATEX_ACRONYM_PACKAGE_GLOSSARIES:
+					g_string_append_printf(out, "\\newacronym{%s}{",temp_str->str);
+					print_latex_node_tree(out, n->children, scratch);
+					g_string_append_printf(out, "}{");
+					break;
+				case LATEX_ACRONYM_PACKAGE_ACRONYM:
+				default:
+					g_string_append_printf(out, "\\newacro{%s}[",temp_str->str);
+					print_latex_node_tree(out, n->children, scratch);
+					g_string_append_printf(out, "]{");
+				break;
+			}
+			trim_trailing_whitespace_in_node_str(n);
 			print_latex_string(out, n->str, scratch);
 			g_string_append_printf(out, "}\n");
-			free(temp);
-			free(width);
+
+			g_string_free(temp_str, FALSE);
 			break;
 		case ABBRSTART:
 			/* Strip out nodes that are being replaced with the abbreviation */
@@ -122,13 +139,29 @@ void print_latex_node(GString *out, node *n, scratch_pad *scratch) {
 			n->next = temp_node->next;
 			temp_node->next = NULL;
 			free_node(temp_node);
+		case ABBRPLURAL:
 		case ABBR:
 			/* In either case, now we call on the abbreviation */
-			width = ascii_label_from_node(n->children->children);
-			temp = ascii_label_from_string(n->children->str);
-			g_string_append_printf(out, "\\ac{%s%s}", width, temp);
-			free(temp);
-			free(width);
+			temp_str = g_string_new("");
+			
+			if(LATEX_ACRONYM_LABEL_STYLE_ABBR == (scratch->latex_acronym_config&LATEX_ACRONYM_LABEL_STYLE_MASK)) {
+				print_raw_node(temp_str, n->children->children);
+			} else {
+				g_string_append(temp_str, ascii_label_from_node(n->children->children));
+				g_string_append(temp_str, ascii_label_from_string(n->children->str));
+			}
+			
+			switch(scratch->latex_acronym_config&LATEX_ACRONYM_PACKAGE_MASK) {
+				case LATEX_ACRONYM_PACKAGE_GLOSSARIES:
+					g_string_append_printf(out, "\\gls%s{%s}", (n->key == ABBRPLURAL) ? "pl" : "", temp_str->str);
+					break;
+				case LATEX_ACRONYM_PACKAGE_ACRONYM:
+				default:
+					g_string_append_printf(out, "\\ac%s{%s}", (n->key == ABBRPLURAL) ? "p" : "", temp_str->str);
+					break;
+			}
+
+			g_string_free(temp_str, FALSE);
 			break;
 		case ABBRSTOP:
 			break;		
@@ -165,7 +198,7 @@ void print_latex_node(GString *out, node *n, scratch_pad *scratch) {
 		case VERBATIMFENCE:
 			pad(out, 2, scratch);
 			if ((n->children != NULL) && (n->children->key == VERBATIMTYPE)) {
-				trim_trailing_whitespace(n->children->str);
+				trim_trailing_whitespace_in_node_str(n->children);
 				if (strlen(n->children->str) > 0) {
 					g_string_append_printf(out, "\\begin{lstlisting}[language=%s]\n%s\\end{lstlisting}", n->children->str,n->str);
 					scratch->padded = 0;
@@ -264,14 +297,28 @@ void print_latex_node(GString *out, node *n, scratch_pad *scratch) {
 			} else if (strcmp(n->str, "mmdheader") == 0) {
 			} else if (strcmp(n->str, "lang") == 0) {
 			} else if (strcmp(n->str, "latexinput") == 0) {
-				trim_trailing_whitespace(n->children->str);
+				trim_trailing_whitespace_in_node_str(n->children);
 				g_string_append_printf(out, "\\input{%s}\n", n->children->str);
 			} else if (strcmp(n->str, "latexfooter") == 0) {
-				trim_trailing_whitespace(n->children->str);
+				trim_trailing_whitespace_in_node_str(n->children);
 				scratch->latex_footer = strdup(n->children->str);
 			} else if (strcmp(n->str, "bibtex") == 0) {
-				trim_trailing_whitespace(n->children->str);
+				trim_trailing_whitespace_in_node_str(n->children);
 				g_string_append_printf(out, "\\def\\bibliocommand{\\bibliography{%s}}\n",n->children->str);
+			} else if (strcmp(n->str, "latexacronympackage") == 0) {
+				temp = label_from_node_tree(n->children);
+				if(strcmp(temp, "acronym") == 0) {
+					scratch->latex_acronym_config = (scratch->latex_acronym_config&LATEX_ACRONYM_LABEL_STYLE_MASK)|LATEX_ACRONYM_PACKAGE_ACRONYM;
+				} else if(strcmp(temp, "glossaries") == 0) {
+					scratch->latex_acronym_config = (scratch->latex_acronym_config&LATEX_ACRONYM_LABEL_STYLE_MASK)|LATEX_ACRONYM_PACKAGE_GLOSSARIES;
+				}
+			} else if (strcmp(n->str, "latexacronymlabelstyle") == 0) {
+				temp = label_from_node_tree(n->children);
+				if(strcmp(temp, "full") == 0) {
+					scratch->latex_acronym_config = (scratch->latex_acronym_config&LATEX_ACRONYM_PACKAGE_MASK)|LATEX_ACRONYM_LABEL_STYLE_FULL;
+				} else if(strncmp(temp, "abbreviation", sizeof("abbr")-1) == 0) {
+					scratch->latex_acronym_config = (scratch->latex_acronym_config&LATEX_ACRONYM_PACKAGE_MASK)|LATEX_ACRONYM_LABEL_STYLE_ABBR;
+				}
 			} else {
 				g_string_append_printf(out, "\\def\\");
 				print_latex_string(out, n->str, scratch);
@@ -281,7 +328,7 @@ void print_latex_node(GString *out, node *n, scratch_pad *scratch) {
 			}
 			break;
 		case METAVALUE:
-			trim_trailing_whitespace(n->str);
+			trim_trailing_whitespace_in_node_str(n);
 			print_latex_string(out,n->str, scratch);
 			break;
 		case FOOTER:
diff --git a/libMultiMarkdown.h b/libMultiMarkdown.h
index 302ce0c..cabd878 100644
--- a/libMultiMarkdown.h
+++ b/libMultiMarkdown.h
@@ -147,17 +147,31 @@ enum keys {
 	VARIABLE,
 	ABBREVIATION,
 	ABBR,
+	ABBRPLURAL,
 	ABBRSTART,
 	ABBRSTOP,
 	TOC,
 	KEY_COUNTER                      /* This *MUST* be the last item in the list */
 };
 
+// The 'correct' data type, i.e., size_t, appears to be unnecessarily big for a
+// node; even an unsigned char might turn out to be sufficient
+#ifdef NODE_LEN_SIZE_T
+typedef size_t NODE_LEN;
+#elif NODE_LEN_UINT
+typedef unsigned int NODE_LEN;
+#else
+typedef unsigned short int NODE_LEN;
+#endif
 
 /* This is the element used in the resulting parse tree */
+// the len element is added in order to prevent iterating
+// The string (e.g., strdup, string trimming) and helps ultimately comparing
+// acronyms much faster.
 struct node {
 	short             key;           /* what type of element are we? */
 	char              *str;          /* relevant string from source for element */
+	NODE_LEN	      len;			 // the length of the string
 	struct link_data  *link_data;    /* store link info when relevant */
 	struct node       *children;     /* child elements */
 	struct node       *next;         /* next element */
diff --git a/lyx.c b/lyx.c
index 6ddff50..c42a720 100644
--- a/lyx.c
+++ b/lyx.c
@@ -666,6 +666,7 @@ void print_lyx_node(GString *out, node *n, scratch_pad *scratch, bool no_newline
 			n->next = temp_node->next;
 			temp_node->next = NULL;
 			free_node(temp_node);
+		case ABBRPLURAL:
 		case ABBR:
 			/* In either case, now we call on the abbreviation */
 //			width = ascii_label_from_node(n->children->children);
@@ -824,7 +825,7 @@ void print_lyx_node(GString *out, node *n, scratch_pad *scratch, bool no_newline
 			g_string_append(out,"\\begin_layout Standard\n");
 			g_string_append(out,"\\begin_inset listings\n");
 			if ((n->children != NULL) && (n->children->key == VERBATIMTYPE)) {
-				trim_trailing_whitespace(n->children->str);
+				trim_trailing_whitespace_in_node_str(n->children);
 				if (strlen(n->children->str) > 0) {
 					// NOTE: the language must match the LyX (LaTex) languages (e.g: Perl, not perl)
 					g_string_append_printf(out, "lstparams \"basicstyle={\\footnotesize\\ttfamily},language=%s\"\n", n->children->str,n->str);
diff --git a/memoir.c b/memoir.c
index eae547a..b63b7ec 100644
--- a/memoir.c
+++ b/memoir.c
@@ -41,7 +41,7 @@ void print_memoir_node(GString *out, node *n, scratch_pad *scratch) {
 		case VERBATIMFENCE:
 			pad(out, 2, scratch);
 			if ((n->children != NULL) && (n->children->key == VERBATIMTYPE)) {
-				trim_trailing_whitespace(n->children->str);
+				trim_trailing_whitespace_in_node_str(n->children);
 				if (strlen(n->children->str) > 0) {
 					g_string_append_printf(out, "\\begin{adjustwidth}{2.5em}{2.5em}\n\\begin{lstlisting}[language=%s]\n", n->children->str);
 					print_raw_node(out, n);
diff --git a/odf.c b/odf.c
index 18b25dc..29f957f 100644
--- a/odf.c
+++ b/odf.c
@@ -84,6 +84,7 @@ void print_odf_node(GString *out, node *n, scratch_pad *scratch) {
 			print_odf_node_tree(out,n->children,scratch);
 			break;
 		case STR:
+		case ABBRPLURAL:
 		case ABBR:
 		case ABBRSTART:
 		case ABBRSTOP:
@@ -270,7 +271,7 @@ void print_odf_node(GString *out, node *n, scratch_pad *scratch) {
 			free(temp);
 			break;
 		case METAVALUE:
-			trim_trailing_whitespace(n->str);
+			trim_trailing_whitespace_in_node_str(n);
 			print_odf_string(out,n->str);
 			break;
 		case FOOTER:
diff --git a/opml.c b/opml.c
index a74413f..a49c123 100644
--- a/opml.c
+++ b/opml.c
@@ -113,7 +113,7 @@ void print_opml_node(GString *out, node *n, scratch_pad *scratch) {
 			g_string_append_printf(out, "str);
 			g_string_append_printf(out, "\" _note=\"");
-			trim_trailing_newlines(n->children->str);
+			trim_trailing_newlines_in_node_str(n->children);
 			print_opml_string(out, n->children->str);
 			g_string_append_printf(out, "\"/>");
 			break;
diff --git a/parse_utilities.c b/parse_utilities.c
index 7a9045a..4a1e000 100644
--- a/parse_utilities.c
+++ b/parse_utilities.c
@@ -26,12 +26,10 @@
 
 /* Create a new node in the parse tree */
 node * mk_node(int key) {
-	node *result = (node *) malloc(sizeof(node));
+	node *result = (node *) calloc(1, sizeof(node));
+	// initializes the node structure in a more flexible way (e.g., if a new
+	// node element like len is introduced)
 	result->key = key;
-	result->str = NULL;
-	result->children = NULL;
-	result->next = NULL;
-	result->link_data = NULL;
 	return result;
 }
 
@@ -39,7 +37,7 @@ node * mk_node(int key) {
 node * mk_str(char *string) {
 	node *result = mk_node(STR);
 	assert(string != NULL);
-	result->str = strdup(string);
+	result->str = strdup_ext(string, &(result->len));
 	return result;
 }
 
@@ -53,6 +51,7 @@ node * mk_str_from_list(node *list, bool extra_newline) {
 		g_string_append(c, "\n");
 
 	result->str = c->str;
+	result->len = c->currentStringLength;
 	
 	g_string_free(c, false);
 	return result;
@@ -107,7 +106,7 @@ node * mk_list(int key, node *list) {
 node * mk_pos_node(int key, char *string, unsigned int start, unsigned int stop) {
 	node *result = mk_node(key);
 	if (string != NULL)
-		result->str = strdup(string);
+		result->str = strdup_ext(string, &(result->len));
 	
 	return result;
 }
@@ -134,6 +133,7 @@ void free_node(node *n) {
 	if (n->str != NULL)
 		free(n->str);
 	n->str = NULL;
+	n->len = 0;
 
 	free_link_data(n->link_data);
 	n->link_data = NULL;
@@ -300,6 +300,8 @@ scratch_pad * mk_scratch_pad(unsigned long extensions) {
 	result->lyx_table_need_line = FALSE;      /* CRC - No table yet */
 	result->lyx_table_total_rows = 0;         /* CRC - No rows */
 	result->lyx_table_total_cols = 0;         /* CRC - No Columns */
+	
+	result->latex_acronym_config = LATEX_ACRONYM_LABEL_STYLE_DEFAULT|LATEX_ACRONYM_PACKAGE_DEFAULT;
 	return result;
 }
 
@@ -769,46 +771,66 @@ char * metavalue_for_key(char *key, node *list) {
 	return result;
 }
 
-/* Trim spaces at end of string */
-void trim_trailing_whitespace(char *str) {
-	unsigned long l;
-	
-	if (str == NULL)
-		return;
+// the trimming character recognition function type
+typedef int (is_trim_char)(int c);
+
+// identifies new line characters
+static inline int is_newline(int c) {
+	return '\n' == c || '\r' == c || '\f' == c;
+}
+
+/**
+ Trims a string according to a trimming character recognition function.
+ 
+ This implementation is significantly faster than the previous one and returns
+ the trimmed string's length.
+ 
+ Example usage:
+ 
+ @code
+ char *s0 = strdup(" 1 23\n\r\f\n\r\f");
+ len = trim_trailing_newlines(s0);
+ printf("-->%s<-- [%lu]\n", s0, len);
+ free(s0);
+ @endcode
+ 
+ @param s
+ The source string to be trimmed.
+ @return The length of the trimmed string s.
+ */
+NODE_LEN trim_trailing_trim_char(char *s, is_trim_char f) {
+	
+	if (s == NULL || !s[0])
+		return 0;
 	
-	l = strlen(str);
+	char *p = s;
 	
-	if (l < 1)
-		return;
+	while(*p) p++;
+	while(--p >= s && f(*p)) ;
 	
-	while ( (l > 0) && (( str[l - 1] == ' ' ) ||
-		( str[l - 1] == '\n' ) || 
-		( str[l - 1] == '\r' ) || 
-		( str[l - 1] == '\t' )) ) {
-		str[l - 1] = '\0';
-		l = strlen(str);
-	}
+	*(p+1)='\0';
+	return (NODE_LEN)((p+1)-s);
 }
 
 /* Trim spaces at end of string */
-void trim_trailing_newlines(char *str) {
-	unsigned long l;
-	
-	if (str == NULL)
-		return;
-	
-	l = strlen(str);
-	
-	if (l < 1)
-		return;
-	
-	while ( (l > 0) && (( str[l - 1] == '\n' ) || 
-	( str[l - 1] == '\r' )) ) {
-		str[l - 1] = '\0';
-		l = strlen(str);
-	}
+inline NODE_LEN trim_trailing_whitespace(char *str) {
+	return trim_trailing_trim_char(str, &isspace);
+}
+
+inline void trim_trailing_whitespace_in_node_str(node *node) {
+	node->len = trim_trailing_whitespace(node->str);
 }
 
+/* Trim spaces at end of string */
+NODE_LEN trim_trailing_newlines(char *str) {
+	return trim_trailing_trim_char(str, &is_newline);
+}
+
+inline void trim_trailing_newlines_in_node_str(node *node) {
+	node->len = trim_trailing_newlines(node->str);
+}
+
+
 /* Return version */
 char * mmd_version(void) {
 	char *result;
@@ -839,10 +861,10 @@ node * copy_node(node *n) {
 	else {
 		node *m = (node *) malloc(sizeof(node));
 
-		*m = *n;
+		memcpy(m, n, sizeof(node)); // explicitly initializes all node elements
 
 		if (n->str != NULL)
-			m->str = strdup(n->str);
+			m->str = strdup(n->str); // len already set by memcpy
 
 		if (n->link_data != NULL) {
 			m->link_data = mk_link_data(n->link_data->label, n->link_data->source, n->link_data->title, copy_node_tree(n->link_data->attr));
@@ -881,4 +903,44 @@ char * my_strndup(const char * source, size_t n) {
 	result[len] = '\0';
 
 	return result;
+}
+
+/**
+ A strdup() replacement returning in addition to the duplicated string 
+ the length of the string.
+ 
+ Warning: In order to prevent repeated casting the length receiving
+ parameter is intentionally chosen to be of type NODE_LEN - instead of
+ using the fixed size_t - thereby matching the type decision taken for the 
+ node's len element.
+ 
+ Example usage:
+ 
+ @code
+ char *s = "test;
+ char *d = NULL;
+ unsigned short len = 0;
+ 
+ d = strdup_ext(s, &len);
+ free(d);
+ @endcode
+ 
+ @param s
+ The source string to be duplicated.
+ @param len
+ A pointer receiving the length of s.
+ @return A copy of the string s. The returned string has to be freed by the 
+ caller.
+ */
+char *strdup_ext(const char *s, NODE_LEN *len) {
+	char *p = NULL;
+	
+	*len = 0;
+	if(s) {
+		*len = (NODE_LEN)strlen(s);
+		if((p = malloc((*len)+1))) {
+			memcpy(p, s, (*len)+1);
+		}
+	}
+	return p;
 }
\ No newline at end of file
diff --git a/parser.h b/parser.h
index 49e0a8e..67dadcb 100644
--- a/parser.h
+++ b/parser.h
@@ -88,6 +88,7 @@ typedef struct {
 	int   lyx_table_total_cols;  /* CRC - The total number of columns in the table */
 	node *lyx_table_caption;     /* CRC - Hold the table caption */
 	GString *lyx_debug_pad;      /* CRC - padding to indent debugging informaiton */
+	unsigned short int latex_acronym_config; // indicates the acronym package and label style to be used
 } scratch_pad;
 
 /* Define smart typography languages -- first in list is default */
@@ -112,6 +113,26 @@ enum smartelements {
 	APOS,
 };
 
+enum latex_acronym_package {
+	LATEX_ACRONYM_PACKAGE_UNDEF = 0,
+	// LaTeX acronym package by To­bias Oetiker
+	// see http://www.ctan.org/tex-archive/macros/latex/contrib/acronym/
+	LATEX_ACRONYM_PACKAGE_ACRONYM,
+	// LaTeX glos­saries pack­age by Ni­cola Tal­bot
+	// see http://www.ctan.org/pkg/glossaries
+	LATEX_ACRONYM_PACKAGE_GLOSSARIES,
+	LATEX_ACRONYM_PACKAGE_DEFAULT = LATEX_ACRONYM_PACKAGE_ACRONYM,
+};
+
+enum latex_acronym_label_style {
+	LATEX_ACRONYM_LABEL_STYLE_UNDEF,
+	LATEX_ACRONYM_LABEL_STYLE_FULL = 1 << 8,
+	LATEX_ACRONYM_LABEL_STYLE_ABBR = 2 << 8,
+	LATEX_ACRONYM_LABEL_STYLE_DEFAULT = LATEX_ACRONYM_LABEL_STYLE_FULL,
+};
+
+static const int LATEX_ACRONYM_PACKAGE_MASK = 0x00FF;
+static const int LATEX_ACRONYM_LABEL_STYLE_MASK = 0xFF00;
 
 
 /* parser utilities declarations */
@@ -155,8 +176,10 @@ void   extract_abbreviations(node *list, scratch_pad *scratch);
 bool   extension(int ext, unsigned long extensions);
 
 /* export utilities */
-void   trim_trailing_whitespace(char *str);
-void   trim_trailing_newlines(char *str);
+NODE_LEN   trim_trailing_whitespace(char *str);
+NODE_LEN   trim_trailing_newlines(char *str);
+void trim_trailing_whitespace_in_node_str(node *node);
+void trim_trailing_newlines_in_node_str(node *node);
 
 /* other utilities */
 char * label_from_string(char *str);
@@ -184,5 +207,6 @@ void debug_node(node *n);
 void debug_node_tree(node *n);
 
 char * my_strndup(const char * source, size_t n);
+char *strdup_ext(const char *s, NODE_LEN *len);
 
 #endif
diff --git a/rtf.c b/rtf.c
index d6cbb6a..c7a6169 100644
--- a/rtf.c
+++ b/rtf.c
@@ -120,7 +120,7 @@ void print_rtf_node(GString *out, node *n, scratch_pad *scratch) {
 			}
 			break;
 		case METAVALUE:
-			trim_trailing_whitespace(n->str);
+			trim_trailing_whitespace_in_node_str(n);
 			print_rtf_string(out, n->str, scratch);
 			break;
 		case BLOCKQUOTEMARKER:
diff --git a/writer.c b/writer.c
index e7bddf6..450a79c 100644
--- a/writer.c
+++ b/writer.c
@@ -231,7 +231,7 @@ void extract_abbreviations(node *list, scratch_pad *scratch) {
 			case ABBREVIATION:
 				temp = copy_node(list);
 				list->key = KEY_COUNTER;	/* Mark this as dead; we will use it elsewhere */
-				trim_trailing_whitespace(temp->str);
+				trim_trailing_whitespace_in_node_str(temp);
 				scratch->abbreviations = cons(temp, scratch->abbreviations);
 				break;
 			case HEADINGSECTION:
@@ -249,12 +249,12 @@ void extract_abbreviations(node *list, scratch_pad *scratch) {
 	}
 }
 
-
 /* find_abbreviations -- use abbreviations to look for matching strings */
 void find_abbreviations(node *list, scratch_pad *scratch) {
 	node *abbr = scratch->abbreviations;
 	node *temp, *target, *end = NULL;
 	bool ismatch;
+	bool isplural = false;
 
 	// Don't look if we didn't define any abbreviations */
 	if (abbr->key == KEY_COUNTER)
@@ -275,8 +275,18 @@ void find_abbreviations(node *list, scratch_pad *scratch) {
 						while((ismatch) && (temp != NULL) && (target != NULL)) {
 							switch (temp->key) {
 								case STR:
-									if (strcmp(temp->str, target->str) != 0) {
+									if (strncmp(temp->str, target->str, temp->len) != 0) {
+										ismatch = false;
+										isplural = false;
+									} else if (temp->len == target->len) {
+										ismatch = true;
+										isplural = false;
+									} else if (1 == target->len - temp->len && 's' == target->str[target->len - 1]) {
+										ismatch = true;
+										isplural = true;
+									} else {
 										ismatch = false;
+										isplural = false;
 									}
 								case SPACE:
 								case KEY_COUNTER:
@@ -299,7 +309,7 @@ void find_abbreviations(node *list, scratch_pad *scratch) {
                                 if (end != NULL)
                                     end->key = ABBRSTOP;
 							} else {
-								list->key = ABBR;
+								list->key = isplural ? ABBRPLURAL : ABBR;
 							}
 						}
 					}