-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
BlockProcessor.java
172 lines (153 loc) · 7.14 KB
/
BlockProcessor.java
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package org.wordpress.android.ui.posts.mediauploadcompletionprocessors;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Document.OutputSettings;
import org.wordpress.android.editor.Utils;
import org.wordpress.android.util.helpers.MediaFile;
import java.util.regex.Matcher;
import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaUploadCompletionProcessorPatterns.PATTERN_BLOCK_CAPTURES;
import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaUploadCompletionProcessorPatterns.PATTERN_SELF_CLOSING_BLOCK_CAPTURES;
/**
* Abstract class to be extended for each enumerated {@link MediaBlockType}.
*/
public abstract class BlockProcessor {
/**
* HTML output used by the parser
*/
@SuppressWarnings("checkstyle:LineLength") static final OutputSettings OUTPUT_SETTINGS = new OutputSettings()
.outline(false)
// .syntax(Syntax.xml)
// Do we want xml or html here (e.g. self closing tags, boolean attributes)?
// https://stackoverflow.com/questions/26584974/keeping-html-boolean-attributes-in-their-original-form-when-parsing-with-jsoup
.prettyPrint(false);
String mLocalId;
String mRemoteId;
String mRemoteUrl;
String mRemoteGuid;
private String mBlockName;
private JsonObject mJsonAttributes;
private Document mBlockContentDocument;
private String mClosingComment;
/**
* @param localId The local media id that needs replacement
* @param mediaFile The mediaFile containing the remote id and remote url
*/
BlockProcessor(String localId, MediaFile mediaFile) {
mLocalId = localId;
mRemoteId = mediaFile.getMediaId();
mRemoteUrl = org.wordpress.android.util.StringUtils.notNullStr(Utils.escapeQuotes(mediaFile
.getOptimalFileURL()));
mRemoteGuid = mediaFile.getVideoPressGuid();
}
private JsonObject parseJson(String blockJson) {
JsonParser parser = new JsonParser();
return parser.parse(blockJson).getAsJsonObject();
}
private Document parseHTML(String blockContent) {
// create document from block content
Document document = Jsoup.parse(blockContent);
document.outputSettings(OUTPUT_SETTINGS);
return document;
}
private boolean splitBlock(String block, Boolean isSelfClosingTag) {
Matcher captures = (
isSelfClosingTag ? PATTERN_SELF_CLOSING_BLOCK_CAPTURES : PATTERN_BLOCK_CAPTURES
).matcher(block);
boolean capturesFound = captures.find();
if (capturesFound) {
mBlockName = captures.group(1);
mJsonAttributes = parseJson(captures.group(2));
mBlockContentDocument = isSelfClosingTag ? null : parseHTML(captures.group(3));
mClosingComment = isSelfClosingTag ? null : captures.group(4);
return true;
} else {
mBlockName = null;
mJsonAttributes = null;
mBlockContentDocument = null;
mClosingComment = null;
return false;
}
}
/**
* Processes a block returning a raw content replacement string. If a match is not found for the block content, this
* method should return the original block contents unchanged.
*
* @param block The raw block contents
* @param isSelfClosingTag True if the block tag is self-closing (e.g. <!-- wp:videopress/video {"id":100} /-->)
* @return A string containing content with ids and urls replaced
*/
String processBlock(String block, Boolean isSelfClosingTag) {
if (splitBlock(block, isSelfClosingTag)) {
if (processBlockJsonAttributes(mJsonAttributes)) {
if (isSelfClosingTag) {
// return injected block
return new StringBuilder()
.append("<!-- wp:")
.append(mBlockName)
.append(" ")
.append(mJsonAttributes) // json parser output
.append(" /-->")
.toString();
} else if (processBlockContentDocument(mBlockContentDocument)) {
// return injected block
return new StringBuilder()
.append("<!-- wp:")
.append(mBlockName)
.append(" ")
.append(mJsonAttributes) // json parser output
.append(" -->\n")
.append(mBlockContentDocument.body().html()) // HTML parser output
.append(mClosingComment)
.toString();
}
} else {
return processInnerBlock(block); // delegate to inner blocks if needed
}
}
// leave block unchanged
return block;
}
String processBlock(String block) {
return processBlock(block, false);
}
/**
* All concrete implementations must implement this method for the particular block type. The document represents
* the html contents of the block to be processed, and is to be mutated in place.<br>
* <br>
* This method should return true to indicate success. Returning false will result in the block contents being
* unmodified.
*
* @param document The document to be mutated to make the necessary replacements
* @return A boolean value indicating whether or not the block contents should be replaced
*/
abstract boolean processBlockContentDocument(Document document);
/**
* All concrete implementations must implement this method for the particular block type. The jsonAttributes object
* is a {@link JsonObject} parsed from the block header attributes. This object can be used to check for a match,
* and can be directly mutated if necessary.<br>
* <br>
* This method should return true to indicate success. Returning false will result in the block contents being
* unmodified.
*
* @param jsonAttributes the attributes object used to check for a match with the local id, and mutated if necessary
* @return
*/
abstract boolean processBlockJsonAttributes(JsonObject jsonAttributes);
/**
* This method can be optionally overriden by concrete implementations to delegate further processing via recursion
* when {@link BlockProcessor#processBlockJsonAttributes(JsonObject)} returns false (i.e. the block did not match
* the local id being replaced). This is useful for implementing mutual recursion with
* {@link MediaUploadCompletionProcessor#processContent(String)} for block types that have media-containing blocks
* within their inner content.<br>
* <br>
* The default implementation provided is a NOOP that leaves the content of the block unchanged.
*
* @param block The raw block contents
* @return A string containing content with ids and urls replaced
*/
String processInnerBlock(String block) {
return block;
}
}