1 | package fr.sii.ogham.core.util; | |
2 | ||
3 | import java.net.URI; | |
4 | import java.net.URISyntaxException; | |
5 | import java.nio.file.Path; | |
6 | import java.nio.file.Paths; | |
7 | import java.util.ArrayList; | |
8 | import java.util.Iterator; | |
9 | import java.util.List; | |
10 | import java.util.regex.Matcher; | |
11 | import java.util.regex.Pattern; | |
12 | ||
13 | import org.jsoup.Jsoup; | |
14 | import org.jsoup.nodes.Document; | |
15 | import org.jsoup.nodes.Element; | |
16 | import org.jsoup.select.Elements; | |
17 | import org.slf4j.Logger; | |
18 | import org.slf4j.LoggerFactory; | |
19 | ||
20 | /** | |
21 | * Utility class for handling HTML content. It helps for repetitive tasks for | |
22 | * manipulating HTML. | |
23 | * | |
24 | * @author Aurélien Baudet | |
25 | * | |
26 | */ | |
27 | public final class HtmlUtils { | |
28 | private static final Logger LOG = LoggerFactory.getLogger(HtmlUtils.class); | |
29 | ||
30 | private static final Pattern HTML_PATTERN = Pattern.compile("<html", Pattern.CASE_INSENSITIVE); | |
31 | private static final String CSS_LINKS_SELECTOR = "link[rel*=\"stylesheet\"], link[type=\"text/css\"], link[href$=\".css\"]"; | |
32 | private static final String HREF_ATTR = "href"; | |
33 | private static final String IMG_SELECTOR = "img"; | |
34 | private static final String SRC_ATTR = "src"; | |
35 | private static final Pattern URL_PATTERN = Pattern.compile("^https?://.+$", Pattern.CASE_INSENSITIVE); | |
36 | private static final Pattern URI_INVALID_CHARS = Pattern.compile("\\\\'"); | |
37 | private static final String URI_ESCAPE = "''"; | |
38 | private static final Pattern QUOTE_ENTITY = Pattern.compile("""); | |
39 | private static final String CSS_URL_FUNC = "(?<start>url\\s*\\(\\s*)(?:'(?<singlequotedurl>\\S*?)'|\"(?<doublequotedurl>\\S*?)\"|(?<unquotedurl>(?:\\\\\\s|\\\\\\)|\\\\\\\"|\\\\\\'|\\S)*?))(?<end>\\s*\\))"; | |
40 | /** | |
41 | * Regular expression that matches CSS {@code url()} inclusions. It can be: | |
42 | * <ul> | |
43 | * <li>url(http://some-url)</li> | |
44 | * <li>url("http://some-url")</li> | |
45 | * <li>url('http://some-url')</li> | |
46 | * </ul> | |
47 | * | |
48 | * <p> | |
49 | * It also handle escaping of quotes. | |
50 | * | |
51 | * <p> | |
52 | * The pattern provides the following named capturing groups: | |
53 | * <ul> | |
54 | * <li>{@code "start"}: matches the {@code url(} part</li> | |
55 | * <li>{@code "end"}: matches the {@code );} part</li> | |
56 | * <li>{@code "singlequotedurl"}: matches the url that is surrounded by | |
57 | * {@literal '} character ({@literal '} is not included)</li> | |
58 | * <li>{@code "doublequotedurl"}: matches the url that is surrounded by | |
59 | * {@literal "} character ({@literal "} is not included)</li> | |
60 | * <li>{@code "unquotedurl"}: matches the url that is not surrounded by a | |
61 | * character</li> | |
62 | * </ul> | |
63 | */ | |
64 | public static final Pattern CSS_URL_FUNC_PATTERN = Pattern.compile(CSS_URL_FUNC); | |
65 | /** | |
66 | * Regular expression that matches CSS properties for image inclusions such | |
67 | * as: | |
68 | * <ul> | |
69 | * <li>{@code background: <value>;}</li> | |
70 | * <li>{@code background-image: <value>};</li> | |
71 | * <li>{@code list-style: <value>};</li> | |
72 | * <li>{@code list-style-image: <value>};</li> | |
73 | * <li>{@code cursor: <value>};</li> | |
74 | * </ul> | |
75 | * | |
76 | * <p> | |
77 | * The pattern provides the following named capturing groups: | |
78 | * <ul> | |
79 | * <li>{@code "property"}: matches the property part (property name, spaces | |
80 | * and {@literal :})</li> | |
81 | * <li>{@code "propertyname"}: matches the property name (such as | |
82 | * {@code background})</li> | |
83 | * <li>{@code "value"}: matches the property value (without final | |
84 | * {@literal ;})</li> | |
85 | * </ul> | |
86 | */ | |
87 | public static final Pattern CSS_IMAGE_PROPERTIES_PATTERN = Pattern.compile("(?<property>(?<propertyname>((background|list-style)(-image)?)|cursor)\\s*:)(?<value>[^;}>]+)", | |
88 | Pattern.MULTILINE | Pattern.DOTALL | Pattern.CASE_INSENSITIVE); | |
89 | ||
90 | /** | |
91 | * Indicates if the provided content is HTML or not. It is considered HTML | |
92 | * only if it is a whole document. Any partial HTML content won't be | |
93 | * considered as HTML. | |
94 | * | |
95 | * @param content | |
96 | * the content to test | |
97 | * @return true if it is HTML, false otherwise | |
98 | */ | |
99 | public static boolean isHtml(String content) { | |
100 |
10
1. isHtml : replaced boolean return with false for fr/sii/ogham/core/util/HtmlUtils::isHtml → NO_COVERAGE 2. isHtml : replaced boolean return with false for fr/sii/ogham/core/util/HtmlUtils::isHtml → SURVIVED 3. isHtml : replaced boolean return with true for fr/sii/ogham/core/util/HtmlUtils::isHtml → SURVIVED 4. isHtml : replaced boolean return with true for fr/sii/ogham/core/util/HtmlUtils::isHtml → SURVIVED 5. isHtml : replaced boolean return with true for fr/sii/ogham/core/util/HtmlUtils::isHtml → NO_COVERAGE 6. isHtml : replaced boolean return with false for fr/sii/ogham/core/util/HtmlUtils::isHtml → TIMED_OUT 7. isHtml : replaced boolean return with true for fr/sii/ogham/core/util/HtmlUtils::isHtml → TIMED_OUT 8. isHtml : replaced boolean return with false for fr/sii/ogham/core/util/HtmlUtils::isHtml → KILLED 9. isHtml : replaced boolean return with false for fr/sii/ogham/core/util/HtmlUtils::isHtml → KILLED 10. isHtml : replaced boolean return with true for fr/sii/ogham/core/util/HtmlUtils::isHtml → KILLED |
return HTML_PATTERN.matcher(content).find(); |
101 | } | |
102 | ||
103 | /** | |
104 | * Finds all CSS file inclusions (looks for <code>link</code> tags for | |
105 | * stylesheet files). Returns only the path or URL to the CSS file. If the | |
106 | * several CSS inclusions have the same path, the path is present in the | |
107 | * list only one time. | |
108 | * | |
109 | * @param htmlContent | |
110 | * the html content that may contain external CSS files | |
111 | * @return the list of found CSS inclusions (paths only) or empty if nothing | |
112 | * found | |
113 | */ | |
114 | public static List<String> getDistinctCssUrls(String htmlContent) { | |
115 | Document doc = Jsoup.parse(htmlContent); | |
116 | Elements els = doc.select(CSS_LINKS_SELECTOR); | |
117 | List<String> cssFiles = new ArrayList<>(els.size()); | |
118 | for (Element e : els) { | |
119 | String path = e.attr(HREF_ATTR); | |
120 |
3
1. getDistinctCssUrls : negated conditional → NO_COVERAGE 2. getDistinctCssUrls : negated conditional → KILLED 3. getDistinctCssUrls : negated conditional → KILLED |
if (!cssFiles.contains(path)) { |
121 | cssFiles.add(path); | |
122 | } | |
123 | } | |
124 |
4
1. getDistinctCssUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::getDistinctCssUrls → NO_COVERAGE 2. getDistinctCssUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::getDistinctCssUrls → TIMED_OUT 3. getDistinctCssUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::getDistinctCssUrls → KILLED 4. getDistinctCssUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::getDistinctCssUrls → KILLED |
return cssFiles; |
125 | } | |
126 | ||
127 | /** | |
128 | * Finds all image inclusions (looks for <code>img</code> tags). Returns | |
129 | * only the path or URL to the image. If the several images have the same | |
130 | * path, the path is present in the list only one time. | |
131 | * | |
132 | * @param htmlContent | |
133 | * the html content that may contain image files | |
134 | * @return the list of found images (paths only) or empty if nothing found | |
135 | */ | |
136 | public static List<String> getDistinctImageUrls(String htmlContent) { | |
137 | Document doc = Jsoup.parse(htmlContent); | |
138 | Elements els = doc.select(IMG_SELECTOR); | |
139 | List<String> images = new ArrayList<>(els.size()); | |
140 | for (Element e : els) { | |
141 | String path = e.attr(SRC_ATTR); | |
142 |
3
1. getDistinctImageUrls : negated conditional → NO_COVERAGE 2. getDistinctImageUrls : negated conditional → KILLED 3. getDistinctImageUrls : negated conditional → KILLED |
if (!images.contains(path)) { |
143 | images.add(path); | |
144 | } | |
145 | } | |
146 |
5
1. getDistinctImageUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::getDistinctImageUrls → SURVIVED 2. getDistinctImageUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::getDistinctImageUrls → NO_COVERAGE 3. getDistinctImageUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::getDistinctImageUrls → TIMED_OUT 4. getDistinctImageUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::getDistinctImageUrls → KILLED 5. getDistinctImageUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::getDistinctImageUrls → KILLED |
return images; |
147 | } | |
148 | ||
149 | /** | |
150 | * Finds all image inclusions from CSS properties. Returns only the path or | |
151 | * URL to the image. If the several images have the same path, the path is | |
152 | * present in the list only one time. | |
153 | * | |
154 | * <p> | |
155 | * It looks for: | |
156 | * <ul> | |
157 | * <li><code>background</code></li> | |
158 | * <li><code>background-image</code></li> | |
159 | * <li><code>list-style</code></li> | |
160 | * <li><code>list-style-image</code></li> | |
161 | * <li><code>cursor</code></li> | |
162 | * </ul> | |
163 | * | |
164 | * @param htmlContent | |
165 | * the html content that may contain image files | |
166 | * @return the list of found images (paths only) or empty if nothing found | |
167 | */ | |
168 | public static List<String> getDistinctCssImageUrls(String htmlContent) { | |
169 | List<String> urls = new ArrayList<>(); | |
170 | Matcher m = CSS_IMAGE_PROPERTIES_PATTERN.matcher(QUOTE_ENTITY.matcher(htmlContent).replaceAll("'")); | |
171 |
5
1. getDistinctCssImageUrls : negated conditional → NO_COVERAGE 2. getDistinctCssImageUrls : negated conditional → TIMED_OUT 3. getDistinctCssImageUrls : negated conditional → KILLED 4. getDistinctCssImageUrls : negated conditional → KILLED 5. getDistinctCssImageUrls : negated conditional → KILLED |
while (m.find()) { |
172 | String value = m.group("value"); | |
173 | Matcher urlMatcher = CSS_URL_FUNC_PATTERN.matcher(value); | |
174 |
3
1. getDistinctCssImageUrls : negated conditional → NO_COVERAGE 2. getDistinctCssImageUrls : negated conditional → KILLED 3. getDistinctCssImageUrls : negated conditional → KILLED |
while (urlMatcher.find()) { |
175 | String url = urlMatcher.group("unquotedurl"); | |
176 |
3
1. getDistinctCssImageUrls : negated conditional → NO_COVERAGE 2. getDistinctCssImageUrls : negated conditional → KILLED 3. getDistinctCssImageUrls : negated conditional → KILLED |
if (url == null) { |
177 | url = urlMatcher.group("singlequotedurl"); | |
178 | } | |
179 |
3
1. getDistinctCssImageUrls : negated conditional → NO_COVERAGE 2. getDistinctCssImageUrls : negated conditional → KILLED 3. getDistinctCssImageUrls : negated conditional → KILLED |
if (url == null) { |
180 | url = urlMatcher.group("doublequotedurl"); | |
181 | } | |
182 |
3
1. getDistinctCssImageUrls : negated conditional → NO_COVERAGE 2. getDistinctCssImageUrls : negated conditional → KILLED 3. getDistinctCssImageUrls : negated conditional → KILLED |
if (!urls.contains(url)) { |
183 | urls.add(url); | |
184 | } | |
185 | } | |
186 | } | |
187 |
5
1. getDistinctCssImageUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::getDistinctCssImageUrls → SURVIVED 2. getDistinctCssImageUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::getDistinctCssImageUrls → NO_COVERAGE 3. getDistinctCssImageUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::getDistinctCssImageUrls → TIMED_OUT 4. getDistinctCssImageUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::getDistinctCssImageUrls → KILLED 5. getDistinctCssImageUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::getDistinctCssImageUrls → KILLED |
return urls; |
188 | } | |
189 | ||
190 | /** | |
191 | * Get the title of the HTML. If no <code>title</code> tag exists, then the | |
192 | * title is null. | |
193 | * | |
194 | * @param htmlContent | |
195 | * the HTML content that may contain a title | |
196 | * @return the title of the HTML or null if none | |
197 | */ | |
198 | public static String getTitle(String htmlContent) { | |
199 | Document doc = Jsoup.parse(htmlContent); | |
200 | Elements titleNode = doc.select("head > title"); | |
201 |
8
1. getTitle : replaced return value with "" for fr/sii/ogham/core/util/HtmlUtils::getTitle → NO_COVERAGE 2. getTitle : negated conditional → NO_COVERAGE 3. getTitle : replaced return value with "" for fr/sii/ogham/core/util/HtmlUtils::getTitle → TIMED_OUT 4. getTitle : negated conditional → TIMED_OUT 5. getTitle : replaced return value with "" for fr/sii/ogham/core/util/HtmlUtils::getTitle → KILLED 6. getTitle : replaced return value with "" for fr/sii/ogham/core/util/HtmlUtils::getTitle → KILLED 7. getTitle : negated conditional → KILLED 8. getTitle : negated conditional → KILLED |
return titleNode.isEmpty() ? null : doc.title(); |
202 | } | |
203 | ||
204 | /** | |
205 | * The list of provided URLs are either relative or absolute. This method | |
206 | * returns only the list of relative URLs. | |
207 | * | |
208 | * <p> | |
209 | * The URL is considered absolute if it starts with {@code "http://"} or | |
210 | * {@code https://}. | |
211 | * | |
212 | * | |
213 | * @param urls | |
214 | * the urls (relative or absolute) | |
215 | * @return the relative urls only | |
216 | */ | |
217 | public static List<String> skipExternalUrls(List<String> urls) { | |
218 |
5
1. skipExternalUrls : negated conditional → NO_COVERAGE 2. skipExternalUrls : negated conditional → TIMED_OUT 3. skipExternalUrls : negated conditional → KILLED 4. skipExternalUrls : negated conditional → KILLED 5. skipExternalUrls : negated conditional → KILLED |
for (Iterator<String> it = urls.iterator(); it.hasNext();) { |
219 | String url = it.next(); | |
220 |
3
1. skipExternalUrls : negated conditional → NO_COVERAGE 2. skipExternalUrls : negated conditional → KILLED 3. skipExternalUrls : negated conditional → KILLED |
if (URL_PATTERN.matcher(url).matches()) { |
221 |
3
1. skipExternalUrls : removed call to java/util/Iterator::remove → NO_COVERAGE 2. skipExternalUrls : removed call to java/util/Iterator::remove → KILLED 3. skipExternalUrls : removed call to java/util/Iterator::remove → KILLED |
it.remove(); |
222 | } | |
223 | } | |
224 |
5
1. skipExternalUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::skipExternalUrls → NO_COVERAGE 2. skipExternalUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::skipExternalUrls → SURVIVED 3. skipExternalUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::skipExternalUrls → TIMED_OUT 4. skipExternalUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::skipExternalUrls → KILLED 5. skipExternalUrls : replaced return value with Collections.emptyList for fr/sii/ogham/core/util/HtmlUtils::skipExternalUrls → KILLED |
return urls; |
225 | } | |
226 | ||
227 | /** | |
228 | * Generate a relative URL/path: | |
229 | * <ul> | |
230 | * <li>If {@code other} parameter is absolute, then return | |
231 | * {@code other}.</li> | |
232 | * <li>If {@code other} parameter is relative, then it merges {@code other} | |
233 | * into {@code base}. For example: | |
234 | * <ul> | |
235 | * <li>base="css/foo.css", other="bar.png" {@literal =>} returns "css/bar.png"</li> | |
236 | * <li>base="css/foo.css", other="../images/bar.png" {@literal =>} returns | |
237 | * "images/bar.png"</li> | |
238 | * <li>base="http://some-url/css/foo.css", other="bar.png" {@literal =>} returns | |
239 | * "http://some-url/css/bar.png"</li> | |
240 | * <li>base="http://some-url/css/foo.css", other="../images/bar.png" {@literal =>} | |
241 | * returns "http://some-url/images/bar.png"</li> | |
242 | * </ul> | |
243 | * </li> | |
244 | * </ul> | |
245 | * | |
246 | * <p> | |
247 | * This method uses {@link #isRelativeUrl(String)} to determine if | |
248 | * {@code other} is relative or absolute. | |
249 | * | |
250 | * @param base | |
251 | * the base path/URL | |
252 | * @param other | |
253 | * the path/URL to relativize | |
254 | * @return the merge path/URL | |
255 | */ | |
256 | public static String relativize(String base, String other) { | |
257 |
3
1. relativize : negated conditional → NO_COVERAGE 2. relativize : negated conditional → KILLED 3. relativize : negated conditional → KILLED |
if (!isRelativeUrl(other)) { |
258 |
2
1. relativize : replaced return value with "" for fr/sii/ogham/core/util/HtmlUtils::relativize → NO_COVERAGE 2. relativize : replaced return value with "" for fr/sii/ogham/core/util/HtmlUtils::relativize → KILLED |
return other; |
259 | } | |
260 | Path basePath = Paths.get(base); | |
261 |
3
1. relativize : replaced return value with "" for fr/sii/ogham/core/util/HtmlUtils::relativize → NO_COVERAGE 2. relativize : replaced return value with "" for fr/sii/ogham/core/util/HtmlUtils::relativize → KILLED 3. relativize : replaced return value with "" for fr/sii/ogham/core/util/HtmlUtils::relativize → KILLED |
return unescapeJavaUri(ResourceUtils.toResourcePath(basePath.resolveSibling(escapeForJavaUri(other)).normalize())); |
262 | } | |
263 | | |
264 | /** | |
265 | * Indicates if the URL is relative or not. | |
266 | * | |
267 | * <p> | |
268 | * Relative URLs may be: | |
269 | * <ul> | |
270 | * <li>{@code "relative/path"}</li> | |
271 | * <li>{@code "./relative/path"}</li> | |
272 | * <li>{@code "../relative/path"}</li> | |
273 | * </ul> | |
274 | * | |
275 | * <p> | |
276 | * On the contrary, any URL that matches one of the following condition is | |
277 | * absolute: | |
278 | * <ul> | |
279 | * <li>starts with a scheme or protocol (like {@code "http://"} or | |
280 | * {@code "classpath:"}</li> | |
281 | * <li>starts with a {@code "/"}</li> | |
282 | * </ul> | |
283 | * | |
284 | * @param url | |
285 | * the URL that may be relative or absolute | |
286 | * @return true if relative | |
287 | */ | |
288 | public static boolean isRelativeUrl(String url) { | |
289 | try { | |
290 |
3
1. isRelativeUrl : negated conditional → NO_COVERAGE 2. isRelativeUrl : negated conditional → KILLED 3. isRelativeUrl : negated conditional → KILLED |
if (url.startsWith("/")) { |
291 |
2
1. isRelativeUrl : replaced boolean return with true for fr/sii/ogham/core/util/HtmlUtils::isRelativeUrl → SURVIVED 2. isRelativeUrl : replaced boolean return with true for fr/sii/ogham/core/util/HtmlUtils::isRelativeUrl → NO_COVERAGE |
return false; |
292 | } | |
293 | URI u = new URI(escapeForJavaUri(url)); | |
294 |
6
1. isRelativeUrl : replaced boolean return with true for fr/sii/ogham/core/util/HtmlUtils::isRelativeUrl → SURVIVED 2. isRelativeUrl : replaced boolean return with true for fr/sii/ogham/core/util/HtmlUtils::isRelativeUrl → NO_COVERAGE 3. isRelativeUrl : negated conditional → NO_COVERAGE 4. isRelativeUrl : replaced boolean return with true for fr/sii/ogham/core/util/HtmlUtils::isRelativeUrl → KILLED 5. isRelativeUrl : negated conditional → KILLED 6. isRelativeUrl : negated conditional → KILLED |
return !u.isAbsolute(); |
295 | } catch (URISyntaxException e) { | |
296 | LOG.warn("Can't determine if '{}' url is relative or absolute => consider absolute", url); | |
297 | LOG.trace("", e); | |
298 |
1
1. isRelativeUrl : replaced boolean return with true for fr/sii/ogham/core/util/HtmlUtils::isRelativeUrl → NO_COVERAGE |
return false; |
299 | } | |
300 | } | |
301 | ||
302 | private static String escapeForJavaUri(String url) { | |
303 |
3
1. escapeForJavaUri : replaced return value with "" for fr/sii/ogham/core/util/HtmlUtils::escapeForJavaUri → NO_COVERAGE 2. escapeForJavaUri : replaced return value with "" for fr/sii/ogham/core/util/HtmlUtils::escapeForJavaUri → KILLED 3. escapeForJavaUri : replaced return value with "" for fr/sii/ogham/core/util/HtmlUtils::escapeForJavaUri → KILLED |
return URI_INVALID_CHARS.matcher(url).replaceAll(URI_ESCAPE); |
304 | } | |
305 | ||
306 | @SuppressWarnings("java:S5361") | |
307 | private static String unescapeJavaUri(String url) { | |
308 |
3
1. unescapeJavaUri : replaced return value with "" for fr/sii/ogham/core/util/HtmlUtils::unescapeJavaUri → NO_COVERAGE 2. unescapeJavaUri : replaced return value with "" for fr/sii/ogham/core/util/HtmlUtils::unescapeJavaUri → KILLED 3. unescapeJavaUri : replaced return value with "" for fr/sii/ogham/core/util/HtmlUtils::unescapeJavaUri → KILLED |
return url.replaceAll(URI_ESCAPE, URI_INVALID_CHARS.pattern()); |
309 | } | |
310 | | |
311 | private HtmlUtils() { | |
312 | super(); | |
313 | } | |
314 | } | |
Mutations | ||
100 |
1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9 10.10 |
|
120 |
1.1 2.2 3.3 |
|
124 |
1.1 2.2 3.3 4.4 |
|
142 |
1.1 2.2 3.3 |
|
146 |
1.1 2.2 3.3 4.4 5.5 |
|
171 |
1.1 2.2 3.3 4.4 5.5 |
|
174 |
1.1 2.2 3.3 |
|
176 |
1.1 2.2 3.3 |
|
179 |
1.1 2.2 3.3 |
|
182 |
1.1 2.2 3.3 |
|
187 |
1.1 2.2 3.3 4.4 5.5 |
|
201 |
1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 |
|
218 |
1.1 2.2 3.3 4.4 5.5 |
|
220 |
1.1 2.2 3.3 |
|
221 |
1.1 2.2 3.3 |
|
224 |
1.1 2.2 3.3 4.4 5.5 |
|
257 |
1.1 2.2 3.3 |
|
258 |
1.1 2.2 |
|
261 |
1.1 2.2 3.3 |
|
290 |
1.1 2.2 3.3 |
|
291 |
1.1 2.2 |
|
294 |
1.1 2.2 3.3 4.4 5.5 6.6 |
|
298 |
1.1 |
|
303 |
1.1 2.2 3.3 |
|
308 |
1.1 2.2 3.3 |