Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bbae0571ae | |||
| a806cf9cae | |||
| 696934016f | |||
| 06d7057e76 | |||
| ef2978fc21 | |||
| 587b10c2b3 | |||
| 6b966a3b5f | |||
| 754915b91f | |||
| 75c0f07eab | |||
| f0946be1fc | |||
| d836297c89 | |||
| d9ff5e891c | |||
| 5cf588ffe3 | |||
| a734ffc933 | |||
| 908188ddf2 |
@@ -407,5 +407,14 @@ traducción inmediata, avisar a Rafa directamente además de pedírselo a Hermes
|
||||
| Asignar `_carta_id` a un artículo | `POST /wp-json/fea/v1/carta-id/{id}` con body `{"carta_id": N}` |
|
||||
| Borrar `_carta_id` de un artículo | `DELETE /wp-json/fea/v1/carta-id/{id}` |
|
||||
|
||||
## Regla para la sección Evangelio y comentarios al Evangelio
|
||||
|
||||
La sección empieza siempre con el enlace al post del evangelio de esa semana (por
|
||||
ejemplo, `MATEO 14, 22-33`) y después van los comentarios. Si ese post no existe,
|
||||
avisad antes de publicar la carta: no empecéis la sección por un comentario.
|
||||
|
||||
No hay que completar ningún campo nuevo. A partir de ese enlace, el sitio asigna la
|
||||
cita bíblica automáticamente a los comentarios nuevos.
|
||||
|
||||
Campos útiles del post: `title`, `content` (HTML), `status` (`draft`/`publish`/`future`),
|
||||
`date` (ISO 8601 para programar), `categories` (array de IDs), `featured_media` (ID de adjunto).
|
||||
|
||||
@@ -105,7 +105,8 @@ add_action('wp_head', function () {
|
||||
--wp--preset--color--base: #0d0d0d;
|
||||
--wp--preset--color--contrast: #ececec;
|
||||
}
|
||||
html[data-fea-theme="dark"] a { color: <?php echo esc_html($accent_dark); ?>; }
|
||||
html[data-fea-theme="dark"] a,
|
||||
html[data-fea-theme="dark"] a:visited { color: <?php echo esc_html($accent_dark); ?>; }
|
||||
html[data-fea-theme="dark"] a:focus-visible,
|
||||
html[data-fea-theme="dark"] button:focus-visible {
|
||||
outline-color: <?php echo esc_html($accent_dark); ?> !important;
|
||||
@@ -184,6 +185,117 @@ add_action('wp_head', function () {
|
||||
color: #b8afa8;
|
||||
}
|
||||
|
||||
/* Comentarios al evangelio: el panel usa colores editoriales claros fijos. */
|
||||
html[data-fea-theme="dark"] .fea-com-evangelio {
|
||||
background: #1a1711;
|
||||
border-left-color: #c9973e;
|
||||
color: #ececec;
|
||||
}
|
||||
html[data-fea-theme="dark"] .fea-com-evangelio-details summary {
|
||||
color: #f0c674;
|
||||
}
|
||||
|
||||
/* Elementos editoriales presentes en artículos y resultados. */
|
||||
html[data-fea-theme="dark"] .fea-audio {
|
||||
background: #161311;
|
||||
border-color: #3a302b;
|
||||
border-left-color: <?php echo esc_html($accent_dark); ?>;
|
||||
}
|
||||
html[data-fea-theme="dark"] .fea-byline-name { color: #ececec; }
|
||||
html[data-fea-theme="dark"] .fea-byline-cat,
|
||||
html[data-fea-theme="dark"] .fea-eed-link .fecha { color: #b8afa8; }
|
||||
html[data-fea-theme="dark"] .fea-share-rule { border-top-color: #3a302b; }
|
||||
html[data-fea-theme="dark"] .fea-share-row { color: #d5cbc3; }
|
||||
html[data-fea-theme="dark"] .fea-share-label,
|
||||
html[data-fea-theme="dark"] .fea-share-sep { color: #b8afa8; }
|
||||
|
||||
/* Formulario y filtros de búsqueda avanzada. */
|
||||
html[data-fea-theme="dark"] .fea-adv-details {
|
||||
background: #161311; border-color: #3a302b;
|
||||
}
|
||||
html[data-fea-theme="dark"] .fea-adv-summary {
|
||||
background: #24181c; border-bottom-color: #3a302b;
|
||||
color: <?php echo esc_html($accent_dark); ?>;
|
||||
}
|
||||
html[data-fea-theme="dark"] .fea-adv-label,
|
||||
html[data-fea-theme="dark"] .fea-adv-chips-label,
|
||||
html[data-fea-theme="dark"] .fea-adv-meta,
|
||||
html[data-fea-theme="dark"] .fea-archive-card .fea-adv-byline { color: #b8afa8; }
|
||||
html[data-fea-theme="dark"] .fea-adv-chip {
|
||||
background: #2a1b20; border-color: #56343d; color: #ececec;
|
||||
}
|
||||
html[data-fea-theme="dark"] .fea-adv-input,
|
||||
html[data-fea-theme="dark"] .fea-adv-select {
|
||||
background: #1c1c1c; border-color: #3a3a3a; color: #ececec;
|
||||
}
|
||||
html[data-fea-theme="dark"] .fea-adv-count { color: #b8afa8; }
|
||||
|
||||
/* Recopilatorios y galería multimedia. */
|
||||
html[data-fea-theme="dark"] .fea-recop-year { border-bottom-color: #3a302b; }
|
||||
html[data-fea-theme="dark"] .fea-recop-list li { border-bottom-color: #2a2320; }
|
||||
html[data-fea-theme="dark"] .fea-recop-list a,
|
||||
html[data-fea-theme="dark"] .fea-mm-title { color: #ececec; }
|
||||
html[data-fea-theme="dark"] .fea-recop-date,
|
||||
html[data-fea-theme="dark"] .fea-mm-date,
|
||||
html[data-fea-theme="dark"] .fea-mm-excerpt { color: #b8afa8; }
|
||||
html[data-fea-theme="dark"] .fea-mm-card { background: #161311; border-color: #2a2320; }
|
||||
html[data-fea-theme="dark"] .fea-mm-thumb { background: #241f1c; }
|
||||
|
||||
/* Galerías de pensamientos y navegación del tema base. */
|
||||
html[data-fea-theme="dark"] .fea-gallery-item {
|
||||
background: #161311; border-color: #3a302b;
|
||||
}
|
||||
html[data-fea-theme="dark"] .fea-lightbox-frame img { background: #161311; }
|
||||
html[data-fea-theme="dark"] .fea-lightbox button { background: #2a2320; color: #ececec; }
|
||||
html[data-fea-theme="dark"] .wp-block-navigation .wp-block-navigation-item__content {
|
||||
color: #ececec;
|
||||
}
|
||||
html[data-fea-theme="dark"] .wp-block-navigation__submenu-container {
|
||||
background: #161311; border-color: #3a302b;
|
||||
}
|
||||
|
||||
/* Página de apoyo: plantilla propia con colores claros explícitos. */
|
||||
html[data-fea-theme="dark"] .fea-support-page {
|
||||
--fea-support-ink: #ececec;
|
||||
--fea-support-warm: #161311;
|
||||
--fea-support-line: #3a302b;
|
||||
--fea-support-card: #161311;
|
||||
}
|
||||
html[data-fea-theme="dark"] .fea-support-hero,
|
||||
html[data-fea-theme="dark"] .fea-support-banner {
|
||||
background: #1a1711 !important;
|
||||
border-color: #3a302b !important;
|
||||
}
|
||||
html[data-fea-theme="dark"] .fea-support-card,
|
||||
html[data-fea-theme="dark"] .fea-support-button.is-secondary {
|
||||
background: #161311 !important;
|
||||
border-color: #3a302b !important;
|
||||
color: #ececec !important;
|
||||
}
|
||||
html[data-fea-theme="dark"] .fea-support-card__title,
|
||||
html[data-fea-theme="dark"] .fea-support-progress__numbers span,
|
||||
html[data-fea-theme="dark"] .fea-support-progress__note,
|
||||
html[data-fea-theme="dark"] .fea-support-sidebar__small { color: #b8afa8; }
|
||||
html[data-fea-theme="dark"] .fea-support-progress__track { background: #3a302b; }
|
||||
|
||||
/* Colaboradores: stylesheet propio del tema con tarjetas claras y !important. */
|
||||
html[data-fea-theme="dark"] .entry-content .wrapper {
|
||||
background: #161311 !important;
|
||||
border-color: #3a302b !important;
|
||||
color: #ececec !important;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,.45) !important;
|
||||
}
|
||||
html[data-fea-theme="dark"] .entry-content .wrapper strong {
|
||||
color: #ececec !important;
|
||||
}
|
||||
html[data-fea-theme="dark"] .entry-content .wrapper a,
|
||||
html[data-fea-theme="dark"] .entry-content .wrapper a:visited {
|
||||
color: #ececec !important;
|
||||
}
|
||||
html[data-fea-theme="dark"] .entry-content .wrapper a:hover {
|
||||
color: <?php echo esc_html($accent_dark); ?> !important;
|
||||
}
|
||||
|
||||
/* Selector de idioma (fea-homepage.php) */
|
||||
html[data-fea-theme="dark"] #fea-lang-btn { border-color: rgba(255,255,255,0.3); }
|
||||
html[data-fea-theme="dark"] #fea-lang-btn:hover,
|
||||
@@ -240,6 +352,10 @@ add_action('wp_footer', function () {
|
||||
?>
|
||||
<script>
|
||||
(function() {
|
||||
// El footer puede renderizarse más de una vez en algunos templates FSE.
|
||||
// Evitamos crear un segundo toggle en ese caso.
|
||||
if (document.getElementById('fea-theme-btn')) return;
|
||||
|
||||
var btn = document.createElement('button');
|
||||
btn.id = 'fea-theme-btn';
|
||||
btn.type = 'button';
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: Fe Adulta — Evangelio en comentarios
|
||||
* Description: Deriva la cita de la carta semanal y muestra la lectura en los comentarios al evangelio.
|
||||
* Version: 1.0
|
||||
*/
|
||||
|
||||
function fea_evangelio_category($es_id) {
|
||||
return function_exists('fea_cat') ? (int) fea_cat($es_id) : (int) $es_id;
|
||||
}
|
||||
|
||||
function fea_evangelio_comment_categories() {
|
||||
return array_map('fea_evangelio_category', [1647, 1646]);
|
||||
}
|
||||
|
||||
function fea_evangelio_is_comment($post_id) {
|
||||
return has_category(fea_evangelio_comment_categories(), $post_id);
|
||||
}
|
||||
|
||||
/** Lee el meta del comentario; las traducciones heredan el dato editorial del original ES. */
|
||||
function fea_evangelio_comment_meta($post_id, $key) {
|
||||
$value = get_post_meta($post_id, $key, true);
|
||||
if ($value !== '' && $value !== null) return $value;
|
||||
if (!function_exists('pll_get_post')) return $value;
|
||||
|
||||
$source_id = (int) pll_get_post((int) $post_id, 'es');
|
||||
return $source_id && $source_id !== (int) $post_id
|
||||
? get_post_meta($source_id, $key, true)
|
||||
: $value;
|
||||
}
|
||||
|
||||
function fea_evangelio_reading_title($cita) {
|
||||
$cita = trim(wp_strip_all_tags((string) $cita));
|
||||
if (!preg_match('/^(Mt|Mc|Lc|Jn)\s+(.+)$/u', $cita, $m)) return '';
|
||||
$books = ['Mt' => 'MATEO', 'Mc' => 'MARCOS', 'Lc' => 'LUCAS', 'Jn' => 'JUAN'];
|
||||
return $books[$m[1]] . ' ' . preg_replace('/\s+/u', ' ', trim($m[2]));
|
||||
}
|
||||
|
||||
/** Localiza la lectura española, sin depender del slug (que puede llevar sufijos). */
|
||||
function fea_evangelio_reading_id($cita) {
|
||||
global $wpdb;
|
||||
static $cache = [];
|
||||
$title = fea_evangelio_reading_title($cita);
|
||||
if ($title === '') return 0;
|
||||
if (isset($cache[$title])) return $cache[$title];
|
||||
// La coincidencia se hace siempre contra la lectura original en español; después
|
||||
// Polylang la sustituye por la traducción del comentario cuando exista.
|
||||
$cat = 1645;
|
||||
$sql = $wpdb->prepare(
|
||||
"SELECT p.ID FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id = p.ID
|
||||
INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
|
||||
WHERE p.post_type = 'post' AND p.post_status = 'publish' AND p.post_title = %s
|
||||
AND tt.taxonomy = 'category' AND tt.term_id = %d
|
||||
ORDER BY p.post_date DESC LIMIT 1",
|
||||
$title, $cat
|
||||
);
|
||||
return $cache[$title] = (int) $wpdb->get_var($sql);
|
||||
}
|
||||
|
||||
/** Devuelve los evangelios enlazados en la sección pertinente de una carta. */
|
||||
function fea_evangelio_from_carta($carta_id) {
|
||||
if (!function_exists('fea_parse_carta_sections')) return [];
|
||||
if (function_exists('pll_get_post')) $carta_id = (int) (pll_get_post((int) $carta_id, 'es') ?: $carta_id);
|
||||
$links = fea_parse_carta_sections((int) $carta_id)['evangelio'] ?? [];
|
||||
$result = [];
|
||||
foreach ($links as $id) {
|
||||
$post = get_post($id);
|
||||
if (!$post) continue;
|
||||
$cita = get_post_meta($id, '* Cita biblica', true);
|
||||
if (!$cita && preg_match('/^(MATEO|MARCOS|LUCAS|JUAN)\s+(.+)$/u', trim($post->post_title), $m)) {
|
||||
$abbr = ['MATEO' => 'Mt', 'MARCOS' => 'Mc', 'LUCAS' => 'Lc', 'JUAN' => 'Jn'][$m[1]];
|
||||
$cita = $abbr . ' ' . $m[2];
|
||||
}
|
||||
if (fea_evangelio_reading_title($cita)) $result[(string) $id] = trim($cita);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/** Busca una carta en las tres categorías históricas, dentro de cuatro días. */
|
||||
function fea_evangelio_nearest_carta_id(WP_Post $post) {
|
||||
$date = mysql2date('Y-m-d', $post->post_date, false);
|
||||
$cartas = get_posts([
|
||||
'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 10,
|
||||
'category__in' => array_map('fea_evangelio_category', [6, 21, 22]),
|
||||
'date_query' => [[
|
||||
'after' => date('Y-m-d', strtotime($date . ' -4 days')),
|
||||
'before' => date('Y-m-d', strtotime($date . ' +4 days')), 'inclusive' => true,
|
||||
]],
|
||||
'orderby' => 'date', 'order' => 'DESC',
|
||||
]);
|
||||
if (!$cartas) return 0;
|
||||
usort($cartas, function($a, $b) use ($post) {
|
||||
return abs(strtotime($a->post_date) - strtotime($post->post_date)) <=> abs(strtotime($b->post_date) - strtotime($post->post_date));
|
||||
});
|
||||
return (int) $cartas[0]->ID;
|
||||
}
|
||||
|
||||
/** Escribe solo metadatos ausentes cuando la carta identifica un único evangelio. */
|
||||
function fea_evangelio_capture_meta($post_id) {
|
||||
$post = get_post($post_id);
|
||||
if (!$post || $post->post_type !== 'post' || $post->post_status !== 'publish' || !fea_evangelio_is_comment($post_id)) return false;
|
||||
if (get_post_meta($post_id, '_cita_evangelio', true)) return false;
|
||||
$carta_id = (int) get_post_meta($post_id, '_carta_id', true);
|
||||
if (!$carta_id) $carta_id = fea_evangelio_nearest_carta_id($post);
|
||||
if (!$carta_id) return false;
|
||||
$evangelios = fea_evangelio_from_carta($carta_id);
|
||||
if (count($evangelios) !== 1) {
|
||||
error_log(sprintf('Fe Adulta: comentario %d sin cita; carta %d tiene %d evangelios.', $post_id, $carta_id, count($evangelios)));
|
||||
return false;
|
||||
}
|
||||
update_post_meta($post_id, '_cita_evangelio', reset($evangelios));
|
||||
if (!get_post_meta($post_id, '_carta_id', true)) update_post_meta($post_id, '_carta_id', $carta_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
function fea_evangelio_summary_label($lang) {
|
||||
$labels = [
|
||||
'es' => 'Leer el evangelio', 'en' => 'Read the Gospel', 'fr' => 'Lire l’Évangile',
|
||||
'it' => 'Leggi il Vangelo', 'pt' => 'Ler o Evangelho',
|
||||
];
|
||||
return $labels[$lang] ?? $labels['es'];
|
||||
}
|
||||
|
||||
add_action('transition_post_status', function($new_status, $old_status, $post) {
|
||||
if ($new_status === 'publish' && $old_status !== 'publish') fea_evangelio_capture_meta($post->ID);
|
||||
}, 20, 3);
|
||||
|
||||
/** Añade referencia y texto plegado, cerrado por defecto, antes del comentario. */
|
||||
add_filter('the_content', function($content) {
|
||||
if (!is_single() || is_admin()) return $content;
|
||||
global $post;
|
||||
if (!$post || !fea_evangelio_is_comment($post->ID)) return $content;
|
||||
$cita = trim((string) fea_evangelio_comment_meta($post->ID, '_cita_evangelio'));
|
||||
if (!$cita) return $content;
|
||||
$reading_id = fea_evangelio_reading_id($cita);
|
||||
$lang = function_exists('pll_current_language') ? pll_current_language() : 'es';
|
||||
if ($reading_id && $lang && $lang !== 'es' && function_exists('pll_get_post')) $reading_id = (int) (pll_get_post($reading_id, $lang) ?: $reading_id);
|
||||
$reading = $reading_id ? get_post($reading_id) : null;
|
||||
$starts_with_cita = preg_match('/^\s*' . preg_quote($cita, '/') . '(?:[\s\.,:;\-–—]|$)/iu', wp_strip_all_tags($content));
|
||||
$html = '<section class="fea-com-evangelio" aria-label="' . esc_attr(fea_evangelio_summary_label($lang)) . '">';
|
||||
if (!$starts_with_cita) $html .= '<p class="fea-com-evangelio-cita">' . esc_html($cita) . '</p>';
|
||||
if ($reading && trim(wp_strip_all_tags($reading->post_content)) !== '') {
|
||||
$html .= '<details class="fea-com-evangelio-details"><summary>' . esc_html(fea_evangelio_summary_label($lang)) . '</summary>'
|
||||
. wpautop(do_shortcode($reading->post_content)) . '</details>';
|
||||
}
|
||||
return $html . '</section>' . $content;
|
||||
}, 8);
|
||||
|
||||
add_action('wp_head', function() {
|
||||
if (!is_single()) return;
|
||||
global $post;
|
||||
if (!$post || !fea_evangelio_is_comment($post->ID)) return;
|
||||
?>
|
||||
<style>
|
||||
.fea-com-evangelio { margin: 0 0 1.75rem; padding: 1rem 1.15rem; background: #f7f4ed; border-left: 4px solid #a77b2d; }
|
||||
.fea-com-evangelio-cita { margin: 0 0 .65rem; font-weight: 700; }
|
||||
.fea-com-evangelio-details summary { cursor: pointer; font-weight: 700; color: #51401e; }
|
||||
.fea-com-evangelio-details > :last-child { margin-bottom: 0; }
|
||||
.fea-com-evangelio-details[open] summary { margin-bottom: .85rem; }
|
||||
</style>
|
||||
<?php
|
||||
});
|
||||
@@ -2044,8 +2044,12 @@ add_filter('the_content', function($content) {
|
||||
global $post;
|
||||
if (!in_category('comentarios-al-evangelio', $post)) return $content;
|
||||
|
||||
$carta_id = get_post_meta($post->ID, '_carta_id', true);
|
||||
$cita = get_post_meta($post->ID, '_cita_evangelio', true);
|
||||
$carta_id = function_exists('fea_evangelio_comment_meta')
|
||||
? fea_evangelio_comment_meta($post->ID, '_carta_id')
|
||||
: get_post_meta($post->ID, '_carta_id', true);
|
||||
$cita = function_exists('fea_evangelio_comment_meta')
|
||||
? fea_evangelio_comment_meta($post->ID, '_cita_evangelio')
|
||||
: get_post_meta($post->ID, '_cita_evangelio', true);
|
||||
|
||||
if (!$carta_id && !$cita) return $content;
|
||||
|
||||
@@ -2066,23 +2070,6 @@ add_filter('the_content', function($content) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Cita del evangelio — link directo al anchor en el índice ---
|
||||
if ($cita) {
|
||||
$book_post_id = ['Mt' => 43908, 'Mc' => 43909, 'Lc' => 43907, 'Jn' => 43906];
|
||||
$abbr = substr($cita, 0, 2);
|
||||
$anchor = fea_cita_to_anchor($cita);
|
||||
$index_url = isset($book_post_id[$abbr])
|
||||
? esc_url(get_permalink($book_post_id[$abbr]) . '#' . $anchor)
|
||||
: '';
|
||||
|
||||
$html .= '<div class="fea-com-row fea-com-cita">'
|
||||
. '<span class="fea-com-label">Evangelio</span>'
|
||||
. ($index_url
|
||||
? '<a href="' . $index_url . '">' . esc_html($cita) . '</a>'
|
||||
: '<span>' . esc_html($cita) . '</span>')
|
||||
. '</div>';
|
||||
}
|
||||
|
||||
// --- Otros comentarios de esta misma cita ---
|
||||
if ($cita) {
|
||||
$others = get_posts([
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/** Propuesta/aplicación segura del backfill de _cita_evangelio. */
|
||||
/** Uso: wp eval-file scripts/backfill_evangelio_meta.php [--apply] --path=/var/www/html */
|
||||
|
||||
// WP-CLI no reenvía de forma consistente argumentos desconocidos a eval-file.
|
||||
// El modo escritura exige por tanto una intención explícita y auditable:
|
||||
// FEA_EVANGELIO_APPLY=1 wp eval-file scripts/backfill_evangelio_meta.php
|
||||
$apply = getenv('FEA_EVANGELIO_APPLY') === '1'
|
||||
|| in_array('--apply', $_SERVER['argv'] ?? [], true);
|
||||
$posts = get_posts([
|
||||
'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => -1,
|
||||
'category__in' => fea_evangelio_comment_categories(),
|
||||
'meta_query' => [['key' => '_cita_evangelio', 'compare' => 'NOT EXISTS']],
|
||||
'orderby' => 'date', 'order' => 'ASC',
|
||||
]);
|
||||
$out = fopen('php://output', 'w');
|
||||
fputcsv($out, ['post_id', 'fecha', 'carta_id', 'cita_propuesta', 'resultado']);
|
||||
foreach ($posts as $post) {
|
||||
$before_carta = (int) get_post_meta($post->ID, '_carta_id', true);
|
||||
// Las cartas deducidas solo se proponen: la cola histórica requiere revisión humana.
|
||||
$carta_id = $before_carta ?: fea_evangelio_nearest_carta_id($post);
|
||||
$evangelios = $carta_id ? fea_evangelio_from_carta($carta_id) : [];
|
||||
$cita = $before_carta && count($evangelios) === 1 ? reset($evangelios) : '';
|
||||
$result = $cita ? ($apply ? 'aplicado' : 'propuesta') : (!$before_carta ? 'revision_manual_carta_por_fecha' : (count($evangelios) > 1 ? 'revision_manual_multi_evangelio' : 'sin_evangelio'));
|
||||
if ($apply && $cita) {
|
||||
update_post_meta($post->ID, '_cita_evangelio', $cita);
|
||||
if (!$before_carta) update_post_meta($post->ID, '_carta_id', $carta_id);
|
||||
}
|
||||
fputcsv($out, [$post->ID, $post->post_date, $carta_id ?: '', $cita, $result]);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
"""doc_convert.py — convierte un .doc viejo a .docx usando Microsoft Word (COM).
|
||||
|
||||
Necesario para Marcos (marroro@telefonica.net), que envía .doc (Word antiguo) y
|
||||
python-docx no sabe leer .doc. El contenido ya viene en EML (lo etiqueta Inma al
|
||||
revisar); aquí solo cambiamos el FORMATO de archivo.
|
||||
|
||||
Requiere Microsoft Word instalado + pywin32 (igual que marcos_review).
|
||||
API: doc_to_docx(path, force=False) -> Path | None
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
WD_FORMAT_DOCX = 16 # wdFormatDocumentDefault (.docx)
|
||||
|
||||
|
||||
def doc_to_docx(path, force: bool = False):
|
||||
"""Convierte un .doc a .docx con Word COM. Idempotente: si el .docx ya existe y
|
||||
no force, lo devuelve sin reconvertir. Devuelve la ruta .docx, o None si falla."""
|
||||
path = Path(path)
|
||||
if path.suffix.lower() != ".doc":
|
||||
return None
|
||||
out = path.with_suffix(".docx")
|
||||
if out.exists() and not force:
|
||||
return out
|
||||
try:
|
||||
import win32com.client # type: ignore
|
||||
except ImportError:
|
||||
return None
|
||||
word = win32com.client.Dispatch("Word.Application")
|
||||
word.Visible = False
|
||||
try:
|
||||
d = word.Documents.Open(str(path))
|
||||
d.SaveAs(str(out), FileFormat=WD_FORMAT_DOCX)
|
||||
d.Close(SaveChanges=False)
|
||||
finally:
|
||||
word.Quit()
|
||||
if out.exists():
|
||||
quitar_smarttags(out)
|
||||
return out if out.exists() else None
|
||||
|
||||
|
||||
def quitar_smarttags(docx_path) -> int:
|
||||
"""Desenvuelve los <w:smartTag> del .docx. Devuelve cuántos ha quitado.
|
||||
|
||||
⚠️ CRÍTICO, no es cosmético. Los .doc de Fray Marcos traen `smartTag` (el viejo
|
||||
reconocimiento de nombres de Word) envolviendo expresiones como "la Iglesia" o
|
||||
"la Cananea". **python-docx solo lee los <w:r> que cuelgan directamente del párrafo,
|
||||
así que el texto de dentro del smartTag DESAPARECE sin dar error**: se publicaron
|
||||
frases mutiladas en las cartas 736, 737 y 739 ("...no va dirigida a los apóstoles,
|
||||
sino a dureza de la respuesta..."). Detectado el 12-ago-2026.
|
||||
"""
|
||||
import re, shutil, zipfile
|
||||
from pathlib import Path
|
||||
|
||||
docx_path = Path(docx_path)
|
||||
with zipfile.ZipFile(docx_path) as z:
|
||||
piezas = {n: z.read(n) for n in z.namelist()}
|
||||
xml = piezas.get("word/document.xml", b"").decode("utf-8", "replace")
|
||||
nuevo, n = re.subn(r"</?w:smartTag(?:Pr)?\b[^>]*>|<w:attr\b[^>]*/>", "", xml)
|
||||
if not n:
|
||||
return 0
|
||||
piezas["word/document.xml"] = nuevo.encode("utf-8")
|
||||
tmp = docx_path.with_suffix(".docx.tmp")
|
||||
with zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED) as z:
|
||||
for nombre, datos in piezas.items():
|
||||
z.writestr(nombre, datos)
|
||||
shutil.move(str(tmp), str(docx_path))
|
||||
return n
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
if len(sys.argv) < 2:
|
||||
print("uso: python doc_convert.py path/al/archivo.doc", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
res = doc_to_docx(Path(sys.argv[1]), force="--force" in sys.argv)
|
||||
print(f"OK: {res}" if res else "FALLO (¿Word/pywin32?)")
|
||||
sys.exit(0 if res else 1)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
# `.doc` originales de Fray Marcos
|
||||
|
||||
Los 30 ficheros `.doc` (Word binario antiguo) tal y como los envía Fray Marcos por correo a
|
||||
`ediciones@feadulta.com`, sin tocar. Subidos a petición de Rafa en el **issue #209**, para poder
|
||||
reproducir y diffear desde este lado el problema de los `<w:smartTag>`.
|
||||
|
||||
## Por qué están aquí
|
||||
|
||||
`python-docx` **no lee el texto que va dentro de un `<w:smartTag>`**: lo salta sin dar error. Los `.doc`
|
||||
de Fray Marcos arrastran esas etiquetas heredadas de versiones viejas de Word envolviendo expresiones
|
||||
como «la Iglesia» o «la Cananea», así que al convertirlos se publicaban frases con palabras borradas en
|
||||
silencio. Está corregido en `scripts/doc_convert.py` con `quitar_smarttags()`.
|
||||
|
||||
Ojo: la cadena `smartTag` **no aparece en el binario `.doc`**; surge al convertirlo a `.docx`, que es
|
||||
formato OOXML. Para verlas hay que convertir primero.
|
||||
|
||||
## Cómo reproducir
|
||||
|
||||
```bash
|
||||
python scripts/doc_convert.py "tests/fixtures/fray-marcos/739_8r_Marcos_d 20 la Cananea.doc"
|
||||
```
|
||||
|
||||
Requiere Word instalado (la conversión va por COM). `doc_convert.py` desenvuelve los smartTag y hace el
|
||||
reemplazo con reintentos, porque Dropbox bloquea el fichero unos segundos justo después de que Word lo
|
||||
cree (WinError 1224).
|
||||
|
||||
## Alcance del problema (barrido cerrado en el #209)
|
||||
|
||||
De los 30 `.doc` revisados, 20 traían smartTag y sumaban 46 fragmentos envueltos, pero solo **3
|
||||
artículos publicados quedaron dañados** (posts `53981`, `55078` y `55659`), ya reparados en producción.
|
||||
Los comentarios de febrero a junio están intactos porque se publicaron por la vía de la migración de
|
||||
Joomla, no desde el `.docx`; solo quedó expuesto lo publicado por Mixbot desde julio.
|
||||
|
||||
## Inventario
|
||||
|
||||
El prefijo numérico es el número de carta; el resto es el nombre original del fichero.
|
||||
|
||||
| carta | semana | fichero original |
|
||||
|---:|---|---|
|
||||
| 713 | semana_VI_TO | `713_8r_Marcos_d 06 plenitud de ley.doc` |
|
||||
| 714 | semana_I_Cuaresma | `714_8r_Marcos_Cuar 1 tentaciones.doc` |
|
||||
| 715 | semana_II_Cuaresma | `715_8r_Marcos_Cuar 2 Transfigurac.doc` |
|
||||
| 716 | semana_III_Cuaresma | `716_8r_Marcos_Cuar 3 Samaritana.doc` |
|
||||
| 717 | semana_IV_Cuaresma | `717_8r_Marcos_Cuar 4 ciego de naci.doc` |
|
||||
| 718 | semana_V_Cuaresma | `718_8r_Marcos_Cuar 5 Lázaro.doc` |
|
||||
| 719 | semana_Domingo_Ramos | `719_8r_Marcos_Cuar 6 Ramos.doc` |
|
||||
| 720 | semana_Santa | `720_8r_Marcos_JUEVES SANTO.doc` |
|
||||
| 720 | semana_Santa | `720_8r_Marcos_Pasc 1 resucitó.doc` |
|
||||
| 720 | semana_Santa | `720_8r_Marcos_VIERNES SANTO.doc` |
|
||||
| 721 | semana_II_Pascua | `721_8r_Marcos_Pasc 2 Tomás.doc` |
|
||||
| 722 | semana_III_Pascua | `722_8r_Marcos_Pasc 3 Emaús.doc` |
|
||||
| 723 | semana_IV_Pascua | `723_8r_Marcos_Pasc 4 pastor modelo.doc` |
|
||||
| 724 | semana_V_Pascua | `724_8r_Marcos_Pasc 5 camino,verdad,vida.doc` |
|
||||
| 725 | semana_VI_Pascua | `725_8r_Marcos_Pasc 6 El Paráclito.doc` |
|
||||
| 726 | semana_VII_Pascua | `726_8r_Marcos_Pasc 7 ascensión.doc` |
|
||||
| 727 | semana_Pentecostes | `727_8r_Marcos_PENTECOSTÉS.doc` |
|
||||
| 728 | semana_Trinidad | `728_8r_Marcos_TRINIDAD.doc` |
|
||||
| 729 | semana_Corpus | `729_8r_Marcos_CORPUS.doc` |
|
||||
| 730 | semana_11_TO | `730_8r_Marcos_d 11 misión de los 12.doc` |
|
||||
| 731 | semana_12_TO | `731_8r_Marcos_d 12 no tengas miedo.doc` |
|
||||
| 732 | semana_13_TO | `732_8r_Marcos_d 13 amor a Dios o família.doc` |
|
||||
| 733 | semana_14_TO | `733_8r_Marcos_d 14 sencillez.doc` |
|
||||
| 734 | semana_15_TO | `734_8r_Marcos_d 15 sembrador.doc` |
|
||||
| 735 | semana_16_TO | `735_8r_Marcos_d 16 cizaña.doc` |
|
||||
| 736 | semana_17_TO | `736_8r_Marcos_d 17 tesoro y perla.doc` |
|
||||
| 737 | semana_18_TO | `737_8r_Marcos_d 18 panes y peces.doc` |
|
||||
| 738 | semana_19_TO | `738_8r_Marcos_d 19 Qué poca fe.doc` |
|
||||
| 739 | semana_20_TO | `739_8r_Marcos_d 20 la Cananea.doc` |
|
||||
| 740 | semana_21_TO | `740_8r_Marcos_d 21 quién es Jesús.doc` |
|
||||
Reference in New Issue
Block a user