1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.importexport.contenthandler;
35
36 import static com.google.common.base.Preconditions.checkState;
37 import static com.google.common.collect.Lists.newArrayList;
38 import static com.google.common.collect.Maps.newTreeMap;
39 import static java.lang.Integer.parseInt;
40 import static org.apache.commons.lang3.StringUtils.*;
41 import static org.yaml.snakeyaml.DumperOptions.FlowStyle.*;
42 import static org.yaml.snakeyaml.DumperOptions.ScalarStyle.*;
43
44 import java.io.IOException;
45 import java.io.OutputStream;
46 import java.io.OutputStreamWriter;
47 import java.util.Collection;
48 import java.util.List;
49 import java.util.SortedMap;
50 import java.util.StringJoiner;
51
52 import org.apache.jackrabbit.util.ISO9075;
53 import org.xml.sax.Attributes;
54 import org.xml.sax.ContentHandler;
55 import org.xml.sax.Locator;
56 import org.xml.sax.SAXException;
57 import org.yaml.snakeyaml.DumperOptions;
58 import org.yaml.snakeyaml.emitter.Emitter;
59 import org.yaml.snakeyaml.events.DocumentEndEvent;
60 import org.yaml.snakeyaml.events.DocumentStartEvent;
61 import org.yaml.snakeyaml.events.ImplicitTuple;
62 import org.yaml.snakeyaml.events.MappingEndEvent;
63 import org.yaml.snakeyaml.events.MappingStartEvent;
64 import org.yaml.snakeyaml.events.ScalarEvent;
65 import org.yaml.snakeyaml.events.SequenceEndEvent;
66 import org.yaml.snakeyaml.events.SequenceStartEvent;
67 import org.yaml.snakeyaml.events.StreamEndEvent;
68 import org.yaml.snakeyaml.events.StreamStartEvent;
69
70 import com.google.common.collect.ImmutableList;
71
72
73
74
75
76 public class YamlContentHandler implements ContentHandler {
77 private final Emitter yaml;
78
79 public YamlContentHandler(OutputStream outputStream) {
80 this.yaml = new Emitter(
81 new OutputStreamWriter(outputStream),
82 new DumperOptions());
83 }
84
85 @Override
86 public void setDocumentLocator(Locator locator) { }
87
88 @Override
89 public void startDocument() throws SAXException {
90 try {
91 emitYamlStart();
92 emitMappingStart();
93 } catch (IOException e) {
94 throw new SAXException(e);
95 }
96 }
97
98 @Override
99 public void endDocument() throws SAXException {
100 try {
101 emitMappingEnd();
102 emitYamlEnd();
103 } catch (IOException e) {
104 throw new SAXException(e);
105 }
106 }
107
108 @Override
109 public void startPrefixMapping(String prefix, String uri) { }
110
111 @Override
112 public void endPrefixMapping(String prefix) { }
113
114 @Override
115 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
116 try {
117 emitKey(ISO9075.decode(qName));
118 emitMappingStart();
119 Collection<Property> properties = parseProperties(attributes);
120 for (Property property : properties) {
121 emitProperty(property);
122 }
123 } catch (IOException e) {
124 throw new SAXException(e);
125 }
126 }
127
128 @Override
129 public void endElement(String uri, String localName, String qName) throws SAXException {
130 try {
131 emitMappingEnd();
132 } catch (IOException e) {
133 throw new SAXException(e);
134 }
135 }
136
137 @Override
138 public void characters(char[] ch, int start, int length) { }
139
140 @Override
141 public void ignorableWhitespace(char[] ch, int start, int length) { }
142
143 @Override
144 public void processingInstruction(String target, String data) { }
145
146 @Override
147 public void skippedEntity(String name) { }
148
149 private static Collection<Property> parseProperties(Attributes attributes) {
150 SortedMap<String, Property> properties = newTreeMap();
151 for (int i = 0; i < attributes.getLength(); i++) {
152 String uri = attributes.getURI(i);
153 String qName = attributes.getQName(i);
154 String localName = attributes.getLocalName(i);
155 String taggedValue = attributes.getValue(i);
156 String type = substringBefore(taggedValue, "!");
157 String value = substringAfter(taggedValue, "!");
158
159 if ("[]".equals(type)) {
160
161 properties.put(qName, new Property(uri, localName, qName, type));
162 } else if (type.startsWith("[")) {
163
164 int index = parseInt(substringBetween(type, "[", "]"));
165 type = substringAfter(type, "]");
166 properties.putIfAbsent(qName, new Property(uri, localName, qName, type));
167 properties.get(qName).addValue(index, value);
168 } else {
169
170 properties.put(qName, new Property(uri, localName, qName, type, value));
171 }
172 }
173 return properties.values();
174 }
175
176 private void emitProperty(Property property) throws IOException {
177 emitKey(property.getQName());
178 if (property.isMulti) {
179 emitSequenceStart();
180 String type = property.getType();
181 for (String value : property.getValues()) {
182 emitValue(value, type);
183 }
184 emitSequenceEnd();
185 } else {
186 emitValue(property.getValue(), property.getType());
187 }
188 }
189
190 private void emitKey(String name) throws IOException {
191 yaml.emit(new ScalarEvent(null, null, new ImplicitTuple(true, true), name, null, null, SINGLE_QUOTED));
192 }
193
194 private void emitValue(String value, String tag) throws IOException {
195 tag = isEmpty(tag) ? null : "!" + tag;
196 yaml.emit(new ScalarEvent(null, tag, new ImplicitTuple(tag == null, tag == null), value, null, null, PLAIN));
197 }
198
199 private void emitYamlStart() throws IOException {
200 yaml.emit(new StreamStartEvent(null, null));
201 yaml.emit(new DocumentStartEvent(null, null, false, null, null));
202 }
203
204 private void emitYamlEnd() throws IOException {
205 yaml.emit(new DocumentEndEvent(null, null, false));
206 yaml.emit(new StreamEndEvent(null, null));
207 }
208
209 private void emitMappingStart() throws IOException {
210 yaml.emit(new MappingStartEvent(null, null, true, null, null, BLOCK));
211 }
212
213 private void emitMappingEnd() throws IOException {
214 yaml.emit(new MappingEndEvent(null, null));
215 }
216
217 private void emitSequenceStart() throws IOException {
218 yaml.emit(new SequenceStartEvent(null, null, true, null, null, FLOW));
219 }
220
221 private void emitSequenceEnd() throws IOException {
222 yaml.emit(new SequenceEndEvent(null, null));
223 }
224
225 private static final class Property {
226 private final String uri;
227 private final String localName;
228 private final String qName;
229 private final List<String> values;
230 private final String type;
231 private boolean isMulti;
232
233 public Property(String uri, String localName, String qName, String type, String value) {
234 this.uri = uri;
235 this.localName = localName;
236 this.qName = qName;
237 this.type = type;
238 this.values = ImmutableList.of(value);
239 this.isMulti = false;
240 }
241
242 public Property(String uri, String localName, String qName, String type) {
243 this.uri = uri;
244 this.localName = localName;
245 this.qName = qName;
246 this.type = type;
247 this.values = newArrayList();
248 this.isMulti = true;
249 }
250
251 public String getUri() {
252 return uri;
253 }
254
255 public String getLocalName() {
256 return localName;
257 }
258
259 public String getQName() {
260 return qName;
261 }
262
263 public String getValue() {
264 return values.get(0);
265 }
266
267 public Iterable<String> getValues() {
268 return values;
269 }
270
271 public void addValue(int index, String value) {
272 checkState(isMulti);
273
274 for (int i = 0; i <= index - values.size(); i++) {
275 values.add(null);
276 }
277 values.set(index, value);
278 }
279
280 public String getType() {
281 return type;
282 }
283
284 public boolean isMulti() {
285 return isMulti;
286 }
287
288 @Override
289 public String toString() {
290 return new StringJoiner(", ", Property.class.getSimpleName() + "[", "]")
291 .add("uri='" + uri + "'")
292 .add("localName='" + localName + "'")
293 .add("qName='" + qName + "'")
294 .add("values='" + values + "'")
295 .add("type='" + type + "'")
296 .add("isMulti='" + isMulti + "'")
297 .toString();
298 }
299 }
300
301 }