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