1   package org.daisy.pipeline.braille.common.saxon.impl;
2   
3   import java.util.ArrayList;
4   import java.util.List;
5   
6   import net.sf.saxon.expr.XPathContext;
7   import net.sf.saxon.lib.ExtensionFunctionCall;
8   import net.sf.saxon.lib.ExtensionFunctionDefinition;
9   import net.sf.saxon.om.Item;
10  import net.sf.saxon.om.Sequence;
11  import net.sf.saxon.om.SequenceIterator;
12  import net.sf.saxon.om.StructuredQName;
13  import net.sf.saxon.trans.XPathException;
14  import net.sf.saxon.value.SequenceExtent;
15  import net.sf.saxon.value.SequenceType;
16  import net.sf.saxon.value.StringValue;
17  
18  import org.daisy.pipeline.braille.common.BrailleTranslator;
19  import org.daisy.pipeline.braille.common.BrailleTranslator.FromStyledTextToBraille;
20  import org.daisy.pipeline.braille.common.BrailleTranslatorRegistry;
21  import org.daisy.pipeline.braille.common.Query;
22  import static org.daisy.pipeline.braille.common.Query.util.query;
23  import static org.daisy.pipeline.braille.common.util.Locales.parseLocale;
24  import org.daisy.pipeline.braille.css.CSSStyledText;
25  
26  import org.osgi.service.component.annotations.Component;
27  import org.osgi.service.component.annotations.Reference;
28  import org.osgi.service.component.annotations.ReferenceCardinality;
29  import org.osgi.service.component.annotations.ReferencePolicy;
30  
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  @Component(
35  	name = "pf:text-transform",
36  	service = { ExtensionFunctionDefinition.class }
37  )
38  @SuppressWarnings("serial")
39  public class TextTransformDefinition extends ExtensionFunctionDefinition {
40  	
41  	private static final StructuredQName funcname = new StructuredQName("pf",
42  			"http://www.daisy.org/ns/pipeline/functions", "text-transform");
43  	
44  	@Reference(
45  		name = "BrailleTranslatorRegistry",
46  		unbind = "-",
47  		service = BrailleTranslatorRegistry.class,
48  		cardinality = ReferenceCardinality.MANDATORY,
49  		policy = ReferencePolicy.STATIC
50  	)
51  	protected void bindBrailleTranslatorRegistry(BrailleTranslatorRegistry registry) {
52  		translatorRegistry = registry.withContext(logger);
53  		logger.debug("Binding BrailleTranslator registry: {}", registry);
54  	}
55  	
56  	private BrailleTranslatorRegistry translatorRegistry;
57  	
58  	public StructuredQName getFunctionQName() {
59  		return funcname;
60  	}
61  	
62  	@Override
63  	public int getMinimumNumberOfArguments() {
64  		return 2;
65  	}
66  	
67  	@Override
68  	public int getMaximumNumberOfArguments() {
69  		return 4;
70  	}
71  	
72  	public SequenceType[] getArgumentTypes() {
73  		return new SequenceType[] {
74  			SequenceType.SINGLE_STRING,
75  			SequenceType.ATOMIC_SEQUENCE,
76  			SequenceType.ATOMIC_SEQUENCE,
77  			SequenceType.ATOMIC_SEQUENCE
78  		};
79  	}
80  	
81  	public SequenceType getResultType(SequenceType[] suppliedArgumentTypes) {
82  		return SequenceType.ATOMIC_SEQUENCE;
83  	}
84  	
85  	public ExtensionFunctionCall makeCallExpression() {
86  		return new ExtensionFunctionCall() {
87  			public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException {
88  				try {
89  					Query query = query(arguments[0].head().getStringValue());
90  					List<String> text = sequenceToList(arguments[1]);
91  					List<CSSStyledText> styledText = new ArrayList<CSSStyledText>();
92  					if (arguments.length > 2) {
93  						List<String> style = sequenceToList(arguments[2]);
94  						if (style.size() != text.size())
95  							throw new XPathException("Lengths of text and style sequences must match");
96  						if (arguments.length > 3) {
97  							List<String> lang = sequenceToList(arguments[3]);
98  							if (lang.size() != text.size())
99  								throw new XPathException("Lengths of text and lang sequences must match");
100 							for (int i = 0; i < text.size(); i++)
101 								styledText.add(new CSSStyledText(text.get(i), style.get(i), parseLocale(lang.get(i)))); }
102 						else
103 							for (int i = 0; i < text.size(); i++)
104 								styledText.add(new CSSStyledText(text.get(i), style.get(i))); }
105 					else
106 						for (int i = 0; i < text.size(); i++)
107 							styledText.add(new CSSStyledText(text.get(i)));
108 					for (BrailleTranslator t : translatorRegistry.getWithHyphenator(query)) {
109 						FromStyledTextToBraille fsttb;
110 						try {
111 							fsttb = t.fromStyledTextToBraille(); }
112 						catch (UnsupportedOperationException e) {
113 							logger.trace("Translator does not implement the FromStyledTextToBraille interface: " + t);
114 							continue; }
115 						try {
116 							return iterableToSequence(fsttb.transform(styledText)); }
117 						catch (Exception e) {
118 							logger.debug("Failed to translate string with translator " + t);
119 							throw e; }
120 					}
121 					throw new XPathException("Could not find a BrailleTranslator for query: " + query); }
122 				catch (XPathException e) {
123 					throw e; }
124 				catch (Throwable e) {
125 					throw new XPathException("Unexpected error in pf:text-transform", e); }
126 			}
127 		};
128 	}
129 	
130 	private static List<String> sequenceToList(Sequence seq) throws XPathException {
131 		List<String> list = new ArrayList<String>();
132 		SequenceIterator iterator = seq.iterate();
133 		for (Item item = iterator.next(); item != null; item = iterator.next())
134 			list.add(item.getStringValue());
135 		return list;
136 	}
137 	
138 	private static Sequence iterableToSequence(Iterable<String> iterable) {
139 		List<StringValue> list = new ArrayList<StringValue>();
140 		for (String s : iterable)
141 			list.add(new StringValue(s));
142 		return new SequenceExtent(list);
143 	}
144 	
145 	private static final Logger logger = LoggerFactory.getLogger(TextTransformDefinition.class);
146 	
147 }