1 package org.daisy.pipeline.tts.config;
2
3 import java.io.File;
4 import java.io.StringReader;
5 import java.net.URI;
6 import java.net.URL;
7 import java.util.Map;
8
9 import javax.xml.transform.sax.SAXSource;
10
11 import net.sf.saxon.s9api.Axis;
12 import net.sf.saxon.s9api.Processor;
13 import net.sf.saxon.s9api.QName;
14 import net.sf.saxon.s9api.XdmNode;
15 import net.sf.saxon.s9api.XdmNodeKind;
16 import net.sf.saxon.s9api.XdmSequenceIterator;
17
18 import org.daisy.common.file.URLs;
19 import org.daisy.common.properties.Properties;
20 import org.daisy.common.properties.Properties.Property;
21
22 import org.osgi.service.component.annotations.Component;
23
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 import org.xml.sax.InputSource;
28
29 public class ConfigReader {
30
31 private static final Logger logger = LoggerFactory.getLogger(ConfigReader.class);
32
33 public interface Extension {
34
35
36
37
38
39 public boolean parseNode(XdmNode node, URI documentURI, ConfigReader parent);
40 }
41
42 private final Processor saxonproc;
43
44 public ConfigReader(Processor saxonproc, Extension... extensions) {
45 this(saxonproc, null, extensions);
46 }
47
48 public ConfigReader(Processor saxonproc, XdmNode doc, Extension... extensions) {
49 this(saxonproc, doc, null, extensions);
50 }
51
52 public ConfigReader(Processor saxonproc, XdmNode doc, Map<String,String> properties, Extension... extensions) {
53 this.saxonproc = saxonproc;
54 String config = properties != null ? TTSConfigProperty.getValue(properties) : TTSConfigProperty.getValue();
55 if (config != null && !"".equals(config)) {
56 XdmNode content = null; {
57
58 if (config.startsWith("file:") || new File(config).exists())
59 content = parseXML(config);
60 else
61
62 try {
63 content = saxonproc.newDocumentBuilder().build(
64 new SAXSource(new InputSource(new StringReader(config))));
65 } catch (Exception e) {
66 logger.debug(
67 "failed to read TTS configuration: not a file path and not valid XML: "
68 + config);
69 }
70 if (content != null)
71 read(content, extensions);
72 }
73 }
74 if (doc != null) {
75 read(doc, extensions);
76 }
77 }
78
79
80
81
82
83
84
85
86 protected XdmNode parseXML(String url, URI base) {
87 try {
88 return parseXML(resolve(url, base), saxonproc);
89 } catch (Exception e) {
90 logger.debug("Malformed URL: " + url);
91 return null;
92 }
93 }
94
95
96 static URL resolve(String url, URI base) {
97 return URLs.asURL(URLs.resolve(base, URLs.asURI(url)));
98 }
99
100
101
102
103
104
105
106 protected XdmNode parseXML(String url) {
107 return parseXML(url, URLs.asURI(new File("./")));
108 }
109
110
111
112
113
114
115 private static XdmNode parseXML(URL url, Processor saxonproc) {
116 if (url == null)
117 return null;
118 try {
119 SAXSource source = new SAXSource(new InputSource(url.openStream()));
120 source.setSystemId(url.toString());
121 return saxonproc.newDocumentBuilder().build(source);
122 } catch (Exception e) {
123 logger.debug("error while reading " + url + ": " + e);
124 return null;
125 }
126 }
127
128 private void read(XdmNode doc, Extension... extensions) {
129 URI docURI = doc.getBaseURI();
130 XdmSequenceIterator it = doc.axisIterator(Axis.CHILD);
131 XdmNode root = doc;
132 while (doc.getNodeKind() != XdmNodeKind.ELEMENT && it.hasNext())
133 root = (XdmNode) it.next();
134 it = root.axisIterator(Axis.CHILD);
135 while (it.hasNext()) {
136 XdmNode node = (XdmNode) it.next();
137 QName qname = node.getNodeName();
138 if (qname != null) {
139 boolean parsed = false;
140 for (int k = 0; !parsed && k < extensions.length; ++k) {
141 if ("css".equalsIgnoreCase(qname.getLocalName())) {
142 logger.warn("Ignoring 'css' element " + node.toString()
143 + " inside TTS config file.\nPlease specify CSS style sheets"
144 + " through the designated option or attach them to the input"
145 + " document.");
146 } else if ("lexicon".equalsIgnoreCase(qname.getLocalName())) {
147 logger.warn("Ignoring 'lexicon' element " + node.toString()
148 + " inside TTS config file.\nPlease specify PLS lexicons"
149 + " through the designated option or attach them to the input"
150 + " document.");
151 } else {
152 parsed = extensions[k].parseNode(node, docURI, this);
153 }
154 }
155 }
156 }
157 }
158
159
160
161 @Component(
162 name = "tts-config-property",
163 immediate = true
164 )
165 protected static class TTSConfigProperty {
166
167 private static final Property PROPERTY = Properties.getProperty("org.daisy.pipeline.tts.config",
168 true,
169 "TTS configuration (XML or file path)",
170 false,
171 null);
172
173 protected TTSConfigProperty() {}
174
175 public static String getValue() {
176 return PROPERTY.getValue();
177 }
178
179 public static String getValue(Map<String,String> snapshot) {
180 return PROPERTY.getValue(snapshot);
181 }
182
183 }
184 }