apex-ruby 1.0.39 → 1.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e6a2086248dff794040d84d718d1d68309370ba7e7c45fe49d4b2fcb1fc81754
|
|
4
|
+
data.tar.gz: 7810a4e7ff7eab5ef22ad8bc0c95ba5a22daeede9257f8ccd74bf27a51863bbc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 045ccea76529a5a5309049c5ca786a84927c573e5a69b9c24b230f4cf62868b4327a2fc3dd7123ee6d7410bc767e820a3265b3eb425a1fe00d5816c7cf8073d6
|
|
7
|
+
data.tar.gz: 1a573a1fe4be54962bfd8841742f86d74ed4271aab401420d17cbc7d8f0cc9f82af7211535be030786704bde6bce419ba3b00bcd93900a4838bf0a37e07ca493
|
|
@@ -21,8 +21,8 @@ typedef struct cmark_node cmark_node;
|
|
|
21
21
|
|
|
22
22
|
#define APEX_VERSION_MAJOR 1
|
|
23
23
|
#define APEX_VERSION_MINOR 1
|
|
24
|
-
#define APEX_VERSION_PATCH
|
|
25
|
-
#define APEX_VERSION_STRING "1.1.
|
|
24
|
+
#define APEX_VERSION_PATCH 46
|
|
25
|
+
#define APEX_VERSION_STRING "1.1.46"
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Processor compatibility modes
|
|
@@ -993,6 +993,17 @@ static bool extract_ial_from_paragraph(cmark_node *para, apex_attributes **attrs
|
|
|
993
993
|
return extract_ial_from_text(text, attrs_out, alds);
|
|
994
994
|
}
|
|
995
995
|
|
|
996
|
+
/* Drop a softbreak or hard break that only separated a removed IAL. */
|
|
997
|
+
static void ial_drop_break_before(cmark_node *node) {
|
|
998
|
+
cmark_node *prev = cmark_node_previous(node);
|
|
999
|
+
if (!prev) return;
|
|
1000
|
+
cmark_node_type prev_type = cmark_node_get_type(prev);
|
|
1001
|
+
if (prev_type == CMARK_NODE_SOFTBREAK || prev_type == CMARK_NODE_LINEBREAK) {
|
|
1002
|
+
cmark_node_unlink(prev);
|
|
1003
|
+
cmark_node_free(prev);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
996
1007
|
/**
|
|
997
1008
|
* Handle span-level IAL (inline elements with attributes)
|
|
998
1009
|
* Example: [Link](url){: .class} or {: #id}
|
|
@@ -1210,9 +1221,17 @@ static bool process_span_ial_in_container(cmark_node *container, ald_entry *alds
|
|
|
1210
1221
|
* Content before IAL can be: a previous sibling (e.g. text + softbreak + IAL text node), or text before IAL in this node (e.g. single node "Text\n{: .lead }"). */
|
|
1211
1222
|
bool has_preceding_content = (cmark_node_previous(child) != NULL) || (ial_start > text);
|
|
1212
1223
|
if (container_type == CMARK_NODE_PARAGRAPH && has_preceding_content) {
|
|
1213
|
-
/* IAL was at end of text - apply to paragraph (e.g. "Text\n{: .lead }" on one block)
|
|
1224
|
+
/* IAL was at end of text - apply to this paragraph (e.g. "Text\n{: .lead }" on one block).
|
|
1225
|
+
* Inside a blockquote, "> {: .class}" follows the paragraph, so the class stays on that paragraph.
|
|
1226
|
+
* On the list marker line, the IAL belongs to the item. A later paragraph inside the item keeps its own class. */
|
|
1227
|
+
cmark_node *attr_node = container;
|
|
1228
|
+
cmark_node *parent = cmark_node_parent(container);
|
|
1229
|
+
if (parent && cmark_node_get_type(parent) == CMARK_NODE_ITEM &&
|
|
1230
|
+
cmark_node_first_child(parent) == container) {
|
|
1231
|
+
attr_node = parent;
|
|
1232
|
+
}
|
|
1214
1233
|
char *attr_str = attributes_to_html(attrs);
|
|
1215
|
-
cmark_node_set_user_data(
|
|
1234
|
+
cmark_node_set_user_data(attr_node, attr_str);
|
|
1216
1235
|
apex_free_attributes(attrs);
|
|
1217
1236
|
|
|
1218
1237
|
/* Remove the IAL from the text node */
|
|
@@ -1235,6 +1254,7 @@ static bool process_span_ial_in_container(cmark_node *container, ald_entry *alds
|
|
|
1235
1254
|
while (end >= new_text && isspace((unsigned char)*end)) *end-- = '\0';
|
|
1236
1255
|
}
|
|
1237
1256
|
if (strlen(new_text) == 0) {
|
|
1257
|
+
ial_drop_break_before(child);
|
|
1238
1258
|
cmark_node_unlink(child);
|
|
1239
1259
|
cmark_node_free(child);
|
|
1240
1260
|
free(new_text);
|
|
@@ -1244,6 +1264,7 @@ static bool process_span_ial_in_container(cmark_node *container, ald_entry *alds
|
|
|
1244
1264
|
}
|
|
1245
1265
|
}
|
|
1246
1266
|
} else {
|
|
1267
|
+
ial_drop_break_before(child);
|
|
1247
1268
|
cmark_node_unlink(child);
|
|
1248
1269
|
cmark_node_free(child);
|
|
1249
1270
|
}
|
|
@@ -1526,6 +1547,138 @@ static bool is_pure_ial_paragraph(cmark_node *para) {
|
|
|
1526
1547
|
* Returns the node to free (if any), or NULL
|
|
1527
1548
|
* Caller must free the returned node after iteration is complete
|
|
1528
1549
|
*/
|
|
1550
|
+
/* Copy attrs without one key="value" attribute. */
|
|
1551
|
+
static char *ial_attrs_without(const char *attrs, const char *name) {
|
|
1552
|
+
size_t nlen = strlen(name);
|
|
1553
|
+
char *out = malloc(strlen(attrs) + 1);
|
|
1554
|
+
if (!out) return NULL;
|
|
1555
|
+
char *write = out;
|
|
1556
|
+
const char *p = attrs;
|
|
1557
|
+
while (*p) {
|
|
1558
|
+
while (*p && isspace((unsigned char)*p)) p++;
|
|
1559
|
+
if (!*p) break;
|
|
1560
|
+
const char *start = p;
|
|
1561
|
+
bool drop = strncmp(p, name, nlen) == 0 && p[nlen] == '=' &&
|
|
1562
|
+
(p[nlen + 1] == '"' || p[nlen + 1] == '\'');
|
|
1563
|
+
if (drop) {
|
|
1564
|
+
char quote = p[nlen + 1];
|
|
1565
|
+
p += nlen + 2;
|
|
1566
|
+
while (*p && *p != quote) p++;
|
|
1567
|
+
if (*p == quote) p++;
|
|
1568
|
+
continue;
|
|
1569
|
+
}
|
|
1570
|
+
while (*p && !isspace((unsigned char)*p)) p++;
|
|
1571
|
+
if (write != out) *write++ = ' ';
|
|
1572
|
+
memcpy(write, start, (size_t)(p - start));
|
|
1573
|
+
write += p - start;
|
|
1574
|
+
}
|
|
1575
|
+
*write = '\0';
|
|
1576
|
+
return out;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
/* True when an HTML block is only a closing tag (and comments), so a
|
|
1580
|
+
* following IAL still belongs to the block before it. */
|
|
1581
|
+
static bool ial_html_is_closer_only(cmark_node *node) {
|
|
1582
|
+
const char *html = cmark_node_get_literal(node);
|
|
1583
|
+
if (!html) return false;
|
|
1584
|
+
for (const char *p = html; *p; p++) {
|
|
1585
|
+
if (*p == '<' && p[1] && p[1] != '/' && p[1] != '!' && p[1] != '?') {
|
|
1586
|
+
return false;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
return true;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
/* Insert IAL attributes into the first opening tag of an HTML block.
|
|
1593
|
+
* An existing class is extended instead of adding a second class attribute. */
|
|
1594
|
+
static char *ial_html_with_attrs(const char *html, const char *attrs) {
|
|
1595
|
+
if (!html || !attrs || !*attrs) return NULL;
|
|
1596
|
+
|
|
1597
|
+
const char *tag = NULL;
|
|
1598
|
+
for (const char *p = html; *p; p++) {
|
|
1599
|
+
if (*p == '<' && p[1] && p[1] != '/' && p[1] != '!' && p[1] != '?') {
|
|
1600
|
+
tag = p;
|
|
1601
|
+
break;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
if (!tag) return NULL;
|
|
1605
|
+
const char *gt = strchr(tag, '>');
|
|
1606
|
+
if (!gt) return NULL;
|
|
1607
|
+
|
|
1608
|
+
const char *class_at = NULL;
|
|
1609
|
+
const char *class_val = NULL;
|
|
1610
|
+
const char *class_end = NULL;
|
|
1611
|
+
for (const char *p = tag + 1; p + 7 < gt; p++) {
|
|
1612
|
+
if ((p == tag + 1 || isspace((unsigned char)p[-1])) && strncmp(p, "class=\"", 7) == 0) {
|
|
1613
|
+
class_at = p;
|
|
1614
|
+
class_val = p + 7;
|
|
1615
|
+
class_end = memchr(class_val, '"', (size_t)(gt - class_val));
|
|
1616
|
+
break;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
const char *ial_class = NULL;
|
|
1621
|
+
const char *ial_class_end = NULL;
|
|
1622
|
+
const char *ial_at = strstr(attrs, "class=\"");
|
|
1623
|
+
if (ial_at && (ial_at == attrs || isspace((unsigned char)ial_at[-1]))) {
|
|
1624
|
+
ial_class = ial_at + 7;
|
|
1625
|
+
ial_class_end = strchr(ial_class, '"');
|
|
1626
|
+
if (!ial_class_end) ial_class = NULL;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
char *extra = NULL;
|
|
1630
|
+
char *merged_class = NULL;
|
|
1631
|
+
if (class_at && class_end && ial_class) {
|
|
1632
|
+
size_t old_len = (size_t)(class_end - class_val);
|
|
1633
|
+
size_t add_len = (size_t)(ial_class_end - ial_class);
|
|
1634
|
+
merged_class = malloc(old_len + add_len + 2);
|
|
1635
|
+
if (!merged_class) return NULL;
|
|
1636
|
+
memcpy(merged_class, class_val, old_len);
|
|
1637
|
+
merged_class[old_len] = ' ';
|
|
1638
|
+
memcpy(merged_class + old_len + 1, ial_class, add_len);
|
|
1639
|
+
merged_class[old_len + 1 + add_len] = '\0';
|
|
1640
|
+
extra = ial_attrs_without(attrs, "class");
|
|
1641
|
+
} else {
|
|
1642
|
+
extra = strdup(attrs);
|
|
1643
|
+
}
|
|
1644
|
+
if (!extra) {
|
|
1645
|
+
free(merged_class);
|
|
1646
|
+
return NULL;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
size_t html_len = strlen(html);
|
|
1650
|
+
size_t extra_len = strlen(extra);
|
|
1651
|
+
size_t merged_len = merged_class ? strlen(merged_class) : 0;
|
|
1652
|
+
char *out = malloc(html_len + extra_len + merged_len + 2);
|
|
1653
|
+
if (!out) {
|
|
1654
|
+
free(extra);
|
|
1655
|
+
free(merged_class);
|
|
1656
|
+
return NULL;
|
|
1657
|
+
}
|
|
1658
|
+
char *write = out;
|
|
1659
|
+
if (merged_class && class_val && class_end) {
|
|
1660
|
+
memcpy(write, html, (size_t)(class_val - html));
|
|
1661
|
+
write += class_val - html;
|
|
1662
|
+
memcpy(write, merged_class, merged_len);
|
|
1663
|
+
write += merged_len;
|
|
1664
|
+
memcpy(write, class_end, (size_t)(gt - class_end));
|
|
1665
|
+
write += gt - class_end;
|
|
1666
|
+
} else {
|
|
1667
|
+
memcpy(write, html, (size_t)(gt - html));
|
|
1668
|
+
write += gt - html;
|
|
1669
|
+
}
|
|
1670
|
+
if (extra_len > 0) {
|
|
1671
|
+
*write++ = ' ';
|
|
1672
|
+
memcpy(write, extra, extra_len);
|
|
1673
|
+
write += extra_len;
|
|
1674
|
+
}
|
|
1675
|
+
memcpy(write, gt, html_len - (size_t)(gt - html) + 1);
|
|
1676
|
+
|
|
1677
|
+
free(extra);
|
|
1678
|
+
free(merged_class);
|
|
1679
|
+
return out;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1529
1682
|
static cmark_node *process_node_ial(cmark_node *node, ald_entry *alds) {
|
|
1530
1683
|
if (!node) return NULL;
|
|
1531
1684
|
|
|
@@ -1570,30 +1723,34 @@ static cmark_node *process_node_ial(cmark_node *node, ald_entry *alds) {
|
|
|
1570
1723
|
/* No span IAL found, fall through to check for next-line IAL */
|
|
1571
1724
|
}
|
|
1572
1725
|
|
|
1573
|
-
/*
|
|
1726
|
+
/* Any block can take an IAL on the following line. */
|
|
1574
1727
|
if (type != CMARK_NODE_HEADING &&
|
|
1575
1728
|
type != CMARK_NODE_PARAGRAPH &&
|
|
1576
1729
|
type != CMARK_NODE_BLOCK_QUOTE &&
|
|
1577
1730
|
type != CMARK_NODE_CODE_BLOCK &&
|
|
1578
1731
|
type != CMARK_NODE_LIST &&
|
|
1579
1732
|
type != CMARK_NODE_ITEM &&
|
|
1580
|
-
type != CMARK_NODE_TABLE
|
|
1733
|
+
type != CMARK_NODE_TABLE &&
|
|
1734
|
+
type != CMARK_NODE_THEMATIC_BREAK &&
|
|
1735
|
+
type != CMARK_NODE_HTML_BLOCK) {
|
|
1581
1736
|
return NULL; /* No node to free */
|
|
1582
1737
|
}
|
|
1583
1738
|
|
|
1584
1739
|
|
|
1585
|
-
/*
|
|
1740
|
+
/* A pure IAL paragraph is the marker, not a block that takes another IAL. */
|
|
1741
|
+
if (type == CMARK_NODE_PARAGRAPH && is_pure_ial_paragraph(node)) {
|
|
1742
|
+
return NULL;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
/* Look at the next sibling for an IAL paragraph. A closing-tag HTML
|
|
1746
|
+
* block can sit between a paragraph and its IAL. */
|
|
1586
1747
|
cmark_node *next = cmark_node_next(node);
|
|
1587
1748
|
if (!next) {
|
|
1588
1749
|
return NULL; /* No node to free */
|
|
1589
1750
|
}
|
|
1590
1751
|
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
cmark_node_type next_type = cmark_node_get_type(next);
|
|
1594
|
-
if (next_type != CMARK_NODE_HTML_BLOCK && next_type != CMARK_NODE_CUSTOM_BLOCK) {
|
|
1595
|
-
break;
|
|
1596
|
-
}
|
|
1752
|
+
while (next && cmark_node_get_type(next) == CMARK_NODE_HTML_BLOCK &&
|
|
1753
|
+
ial_html_is_closer_only(next)) {
|
|
1597
1754
|
next = cmark_node_next(next);
|
|
1598
1755
|
}
|
|
1599
1756
|
|
|
@@ -1605,10 +1762,18 @@ static cmark_node *process_node_ial(cmark_node *node, ald_entry *alds) {
|
|
|
1605
1762
|
if (is_pure_ial_paragraph(next)) {
|
|
1606
1763
|
apex_attributes *attrs = NULL;
|
|
1607
1764
|
if (extract_ial_from_paragraph(next, &attrs, alds)) {
|
|
1608
|
-
/* Store attributes in this node */
|
|
1609
1765
|
char *attr_str = attributes_to_html(attrs);
|
|
1610
|
-
cmark_node_set_user_data(node, attr_str);
|
|
1611
1766
|
apex_free_attributes(attrs);
|
|
1767
|
+
if (type == CMARK_NODE_HTML_BLOCK) {
|
|
1768
|
+
const char *literal = cmark_node_get_literal(node);
|
|
1769
|
+
char *merged = ial_html_with_attrs(literal, attr_str);
|
|
1770
|
+
free(attr_str);
|
|
1771
|
+
if (!merged) return NULL;
|
|
1772
|
+
cmark_node_set_literal(node, merged);
|
|
1773
|
+
free(merged);
|
|
1774
|
+
} else {
|
|
1775
|
+
cmark_node_set_user_data(node, attr_str);
|
|
1776
|
+
}
|
|
1612
1777
|
|
|
1613
1778
|
/* Return node to be unlinked and freed after iteration completes */
|
|
1614
1779
|
/* Don't unlink here - that invalidates the iterator */
|
|
@@ -1642,6 +1807,14 @@ void apex_process_ial_in_tree(cmark_node *node, ald_entry *alds) {
|
|
|
1642
1807
|
/* Process node and collect any nodes that need to be freed */
|
|
1643
1808
|
cmark_node *node_to_free = process_node_ial(cur, alds);
|
|
1644
1809
|
if (node_to_free) {
|
|
1810
|
+
bool already_queued = false;
|
|
1811
|
+
for (size_t i = 0; i < free_count; i++) {
|
|
1812
|
+
if (nodes_to_free[i] == node_to_free) {
|
|
1813
|
+
already_queued = true;
|
|
1814
|
+
break;
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
if (already_queued) continue;
|
|
1645
1818
|
/* Expand array if needed */
|
|
1646
1819
|
if (free_count >= free_capacity) {
|
|
1647
1820
|
free_capacity = free_capacity ? free_capacity * 2 : 16;
|
|
@@ -2908,6 +3081,42 @@ static bool ial_append_verbatim(char **buf, char **write, size_t *remaining,
|
|
|
2908
3081
|
/**
|
|
2909
3082
|
* Preprocess markdown to extract image attributes and URL-encode all link URLs
|
|
2910
3083
|
*/
|
|
3084
|
+
/* Closing ')' of a link or image destination.
|
|
3085
|
+
* Quotes hide parentheses, and unquoted '(' must be balanced, so a title
|
|
3086
|
+
* like "Output options (Markdown)" does not end the destination early. */
|
|
3087
|
+
static const char *ial_destination_closer(const char *start) {
|
|
3088
|
+
int depth = 1;
|
|
3089
|
+
bool in_quote = false;
|
|
3090
|
+
char quote = 0;
|
|
3091
|
+
const char *p = start;
|
|
3092
|
+
|
|
3093
|
+
if (!start) return NULL;
|
|
3094
|
+
while (*p && *p != '\n' && *p != '\r') {
|
|
3095
|
+
if (in_quote) {
|
|
3096
|
+
if (*p == '\\' && p[1]) {
|
|
3097
|
+
p += 2;
|
|
3098
|
+
continue;
|
|
3099
|
+
}
|
|
3100
|
+
if (*p == quote) in_quote = false;
|
|
3101
|
+
p++;
|
|
3102
|
+
continue;
|
|
3103
|
+
}
|
|
3104
|
+
if (*p == '"' || *p == '\'') {
|
|
3105
|
+
in_quote = true;
|
|
3106
|
+
quote = *p++;
|
|
3107
|
+
continue;
|
|
3108
|
+
}
|
|
3109
|
+
if (*p == '(') {
|
|
3110
|
+
depth++;
|
|
3111
|
+
} else if (*p == ')') {
|
|
3112
|
+
depth--;
|
|
3113
|
+
if (depth == 0) return p;
|
|
3114
|
+
}
|
|
3115
|
+
p++;
|
|
3116
|
+
}
|
|
3117
|
+
return NULL;
|
|
3118
|
+
}
|
|
3119
|
+
|
|
2911
3120
|
char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_attrs, apex_mode_t mode) {
|
|
2912
3121
|
if (!text) return NULL;
|
|
2913
3122
|
|
|
@@ -2976,12 +3185,10 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
|
|
|
2976
3185
|
const char *attr_start = NULL;
|
|
2977
3186
|
const char *paren_end = NULL;
|
|
2978
3187
|
|
|
2979
|
-
/* Find the closing paren first
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
if (
|
|
2983
|
-
paren_end = p;
|
|
2984
|
-
} else {
|
|
3188
|
+
/* Find the closing paren first. Parentheses inside a quoted
|
|
3189
|
+
* title, and balanced parentheses in the URL, do not count. */
|
|
3190
|
+
paren_end = ial_destination_closer(url_start);
|
|
3191
|
+
if (!paren_end) {
|
|
2985
3192
|
/* Malformed - skip just the ! and continue */
|
|
2986
3193
|
read = img_start + 1;
|
|
2987
3194
|
continue;
|
|
@@ -4064,12 +4271,13 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
|
|
|
4064
4271
|
const char *url_end = NULL;
|
|
4065
4272
|
const char *paren_end = NULL;
|
|
4066
4273
|
|
|
4067
|
-
/* Find the closing paren first
|
|
4068
|
-
|
|
4069
|
-
|
|
4274
|
+
/* Find the closing paren first. A quoted title may contain
|
|
4275
|
+
* parentheses; stop at a newline when the destination never closes. */
|
|
4276
|
+
paren_end = ial_destination_closer(url_start);
|
|
4277
|
+
if (!paren_end) {
|
|
4278
|
+
p = url_start;
|
|
4279
|
+
while (*p && *p != '\n' && *p != '\r') p++;
|
|
4070
4280
|
paren_end = p;
|
|
4071
|
-
} else {
|
|
4072
|
-
paren_end = p; /* End at newline or end of string */
|
|
4073
4281
|
}
|
|
4074
4282
|
|
|
4075
4283
|
/* Scan forward looking for titles: "title", 'title', or (title) */
|
|
@@ -4483,10 +4691,9 @@ char *apex_preprocess_image_attributes(const char *text, image_attr_entry **img_
|
|
|
4483
4691
|
const char *attr_start = NULL;
|
|
4484
4692
|
const char *paren_end = NULL;
|
|
4485
4693
|
|
|
4486
|
-
/* Find closing paren */
|
|
4487
|
-
|
|
4488
|
-
if (
|
|
4489
|
-
paren_end = p;
|
|
4694
|
+
/* Find closing paren, skipping parentheses inside quotes. */
|
|
4695
|
+
paren_end = ial_destination_closer(url_start);
|
|
4696
|
+
if (paren_end) {
|
|
4490
4697
|
p = url_start;
|
|
4491
4698
|
|
|
4492
4699
|
/* Look for attributes */
|
|
@@ -350,6 +350,7 @@ typedef struct {
|
|
|
350
350
|
int strong_count;
|
|
351
351
|
int emph_count;
|
|
352
352
|
int code_inline_count;
|
|
353
|
+
int hr_count;
|
|
353
354
|
} element_counters;
|
|
354
355
|
|
|
355
356
|
/**
|
|
@@ -482,6 +483,7 @@ static void collect_nodes_with_attrs_recursive(cmark_node *node, attr_node **lis
|
|
|
482
483
|
else if (type == CMARK_NODE_LIST) elem_idx = counters->list_count++;
|
|
483
484
|
else if (type == CMARK_NODE_ITEM) elem_idx = counters->item_count++;
|
|
484
485
|
else if (type == CMARK_NODE_CODE_BLOCK) elem_idx = counters->code_count++;
|
|
486
|
+
else if (type == CMARK_NODE_THEMATIC_BREAK) elem_idx = counters->hr_count++;
|
|
485
487
|
/* Inline elements need indices too - each type has its own counter */
|
|
486
488
|
else if (type == CMARK_NODE_LINK) elem_idx = counters->link_count++;
|
|
487
489
|
else if (type == CMARK_NODE_IMAGE) elem_idx = counters->image_count++;
|
|
@@ -623,6 +625,9 @@ char *apex_render_html_with_attributes(cmark_node *document, int options) {
|
|
|
623
625
|
} else if (tag_len == 2 && memcmp(tag_start, "li", 2) == 0) {
|
|
624
626
|
elem_type = CMARK_NODE_ITEM;
|
|
625
627
|
elem_idx = html_counters.item_count++;
|
|
628
|
+
} else if (tag_len == 2 && memcmp(tag_start, "hr", 2) == 0) {
|
|
629
|
+
elem_type = CMARK_NODE_THEMATIC_BREAK;
|
|
630
|
+
elem_idx = html_counters.hr_count++;
|
|
626
631
|
} else if (tag_len == 3 && memcmp(tag_start, "pre", 3) == 0) {
|
|
627
632
|
elem_type = CMARK_NODE_CODE_BLOCK;
|
|
628
633
|
elem_idx = html_counters.code_count++;
|
|
@@ -776,13 +781,19 @@ char *apex_render_html_with_attributes(cmark_node *document, int options) {
|
|
|
776
781
|
}
|
|
777
782
|
html_fingerprint[fp_idx] = '\0';
|
|
778
783
|
} else {
|
|
779
|
-
/* For block elements, extract text content
|
|
784
|
+
/* For block elements, extract text content.
|
|
785
|
+
* Paragraph attributes are fingerprinted from the first text
|
|
786
|
+
* node, which ends at a softbreak, so stop at the first newline. */
|
|
780
787
|
const char *content_start = tag_name_end;
|
|
781
788
|
while (*content_start && *content_start != '>') content_start++;
|
|
782
789
|
if (*content_start == '>') content_start++;
|
|
783
790
|
|
|
784
791
|
const char *text_p = content_start;
|
|
785
792
|
while (*text_p && *text_p != '<' && fp_idx < 50) {
|
|
793
|
+
if (elem_type == CMARK_NODE_PARAGRAPH &&
|
|
794
|
+
(*text_p == '\n' || *text_p == '\r')) {
|
|
795
|
+
break;
|
|
796
|
+
}
|
|
786
797
|
html_fingerprint[fp_idx++] = *text_p++;
|
|
787
798
|
}
|
|
788
799
|
html_fingerprint[fp_idx] = '\0';
|
data/lib/apex/version.rb
CHANGED