LINUX.ORG.RU

Emacs, добавление заголовка к ссылке в org-mode

 , ,


0

1

Очень плохо понимаю язык Lisp, тем более ELisp. Нужна ваша помощь. В org-mode есть функция для добавления ссылки (Ctrl-x Ctrl-l). Но название ссылки надо вводить руками. Я нашел код, чтобы вставлять текст из <title></title>, но кириллица отображается неверно. Вопросы:

  1. Как получить кириллицу в правильной кодировке?
  2. Как вызывать эту функцию только если это ссылка на веб-страницу? (не на файл и т.п.)
;; Function gets title from a HTML page
(defun org-link-describe (url &optional descr)
  (let ((buffer (url-retrieve-synchronously url))
        (title nil))
    (save-excursion
      (set-buffer buffer)
      (goto-char (point-min))
      (search-forward-regexp "<title>\\([^<]+?\\)</title>")
      (setq title (match-string 1 ) )
      (kill-buffer (current-buffer)))
    title))

;; Fill a description for a link
(setq org-make-link-description-function 'org-link-describe)

У меня org-insert-link висит на C-c C-l (по умолчанию, не менял).

(org-insert-link &optional COMPLETE-FILE LINK-LOCATION DESCRIPTION)

Insert a link.  At the prompt, enter the link.

Можно запилить функцию, которая будет запрашивать у пользователя LINK-LOCATION, потом, если это ссылка на веб-страницу (https?), получать DESCRIPTION от org-link-describe и вызывать org-insert-link с обоими этими параметрами, иначе только с LINK-LOCATION. Повесить ее на нужное сочетание (например, C-c C-l).

Nervous ★★★★★ ()
Последнее исправление: Nervous (всего исправлений: 4)

Как получить кириллицу в правильной кодировке?

Попробуй для начала вставить (set-buffer-multibyte t). Вот так:

(defun org-link-describe (url &optional descr)
  (let ((buffer (url-retrieve-synchronously url))
        (title nil))
    (save-excursion
      (set-buffer buffer)
      (set-buffer-multibyte t)
      (goto-char (point-min))
      (search-forward-regexp "<title>\\([^<]+?\\)</title>")
      (setq title (match-string 1 ) )
      (kill-buffer (current-buffer)))
    title))

Но я ожидаю подводный камень: сайты, которые в кодировке koi8-r, например, с заголовком подведут. utf-8 работать должна. Даже китайские сайты (тот же https://baidu.com) должны быть ок. Тут надо более умно поступить, строчки вытаскивать надо с учетом кодировки, в которой сайт отдает страничку. Или же вытянуть сайт, выяснить его кодировку и полученную строчку из title перекодировать. Это сейчас надо в документацию залезть.

Zubok ★★★★★ ()
Последнее исправление: Zubok (всего исправлений: 2)
Ответ на: комментарий от Zubok

На opennet.ru не работает.

Всю страницу скачивать необязательно. Кодировка обычно указывается наверху секции head. Так что можно искать по слову «charset».

<meta http-equiv="Content-Type" content="text/html; charset=koi8-r">

А потом, с учетом этой информации, задавать кодировку буфера. Но я такой код написать не могу.

Много ли таких сайтов, с 8 битной кодировкой? Я так не думаю.

o_-- ()
Ответ на: комментарий от o_--

Всю страницу скачивать необязательно. Кодировка обычно указывается наверху секции head. Так что можно искать по слову «charset».

В хидере кодировка обычно идет. Content-Type: ... charset=<кодировка>.

Я нашел, кажется, нужное. Функция url-insert. Если она работает, как это заявлено, то она в соответствии с кодировкой из хидера должна перекодировать. Просто сейчас я тут занят немного, проверить не смогу прямо сейчас.

(url-insert BUFFER &optional BEG END)

Insert the body of a URL object.

BUFFER should be a complete URL buffer as returned by ‘url-retrieve’.
If the headers specify a coding-system (and current buffer is multibyte),
it is applied to the body before it is inserted.

Returns a list of the form (SIZE CHARSET), where SIZE is the size in bytes
of the inserted text and CHARSET is the charset that was specified in the header,
or nil if none was found.

BEG and END can be used to only insert a subpart of the body.
They count bytes from the beginning of the body.
Zubok ★★★★★ ()
Ответ на: комментарий от Zubok

да, url-insert работает:

(defun org-link-describe (url)
  (with-temp-buffer
    (let ((data-buffer (url-retrieve-synchronously url)))
      (url-insert data-buffer)
      (kill-buffer data-buffer)
      (goto-char (point-min))
      (if (re-search-forward "<title>\\([^<]+?\\)</title>")
          (match-string-no-properties 1)))))
openvg ()
Ответ на: комментарий от openvg

Заработало, когда добавил «descr».

(defun org-link-describe (url &optional descr)
...
o_-- ()
Ответ на: комментарий от openvg

Таким образом вопрос 1 полностью закрывается кодом:

;; Function gets title from a HTML page
(defun org-link-describe (url &optional descr)
  (with-temp-buffer
    (let ((data-buffer (url-retrieve-synchronously url)))
      (url-insert data-buffer)
      (kill-buffer data-buffer)
      (goto-char (point-min))
      (if (re-search-forward "<title>\\([^<]+?\\)</title>")
          (match-string-no-properties 1)))))

;; Fill a description for a link
(setq org-make-link-description-function 'org-link-describe)

Спасибо всем участникам!

По поводу проверки ссылки… Всё работает и без проверки. Видимо, url-retrieve-synchronously возвращает ошибку, если переменная url не соответствует формату HTTP ссылки. Мои догадки верны?

o_-- ()
Ответ на: комментарий от o_--

а что должна вернуть функция org-link-describe если url это не валидная ссылка на веб страницу?

эта функция вернет nil если это не веб ссылка (она так же вернет nil если заголовок не будет найден):

(defun org-link-describe (url &optional descr)
  (when (url-host (url-generic-parse-url url))
    (with-temp-buffer
      (let ((data-buffer (url-retrieve-synchronously url)))
        (url-insert data-buffer)
        (kill-buffer data-buffer)
        (goto-char (point-min))
        (if (re-search-forward "<title>\\([^<]+?\\)</title>" nil t)
            (match-string-no-properties 1))))))

openvg ()
Ответ на: комментарий от o_--

Воистину работает, и велосипеды-обертки городить не надо.

Nervous ★★★★★ ()
Ответ на: комментарий от openvg

небольшой рефакторинг:

(defun org-link-describe (url &optional descr)
  (when (url-type (url-generic-parse-url url))
    (with-temp-buffer
      (url-insert-file-contents url)
      (when (re-search-forward "<title>\\([^<]+?\\)</title>" nil t)
        (match-string-no-properties 1)))))

openvg ()
Ответ на: комментарий от openvg

да, url-insert работает:

Угу. Я сейчас попробовал у себя наконец-то. Заметил одну непокрытую проблему. Есть сайты, которые кодировку не передают (плохо себя ведут, да), поэтому перекодировка обламывается. В случае отсутствия кодировки url-insert передает как есть, не перекодирует, а проверять теги <meta> в тексте страницы не обучен. И вот тут облом. Например, взял японский https://rakuten.co.jp, а он не передает charset= в хидере. Поэтому возвращается однобайтовый заголовок. Я думаю, что парсить <meta> пока не стоит. Проще можно считать charset по умолчанию utf-8, если нет кодировки. Это должно покрыть большинство случаев, кроме случаев, когда сайт в странной кодировке и она не указана в хидере, а только в <meta> (такие запросто могут встетиться). Можно просто сделать буфер data-buffer мультибайтным. Тогда rakuten.co.jp нормально заголовок отдает.

(defun org-link-describe (url &optional descr)
  (with-temp-buffer
    (let ((data-buffer (url-retrieve-synchronously url)))
      (with-current-buffer data-buffer
          (set-buffer-multibyte t))
      (url-insert data-buffer)
      (kill-buffer data-buffer)
      (goto-char (point-min))
      (if (re-search-forward "<title>\\([^<]+?\\)</title>")
          (match-string-no-properties 1)))))

Мне только не нравится, что этот url-insert перекодирует весь большой сайт, создавая еще один большой буфер, когда надо перекодировать только заголовок. Обратил внимание на аргументы BEG и END у url-insert. Вроде показалось, что можно воспользоваться и проставить (match-beginning 1) и (match-end 1) вместо них. И кажется, что перекодируется только <title>. Проверил — нифига. Не совсем ясно написано в документации. Эксперимент показывает, что это начало и конец кусочка непосредствено в теле странички, а не во всем буфере вместе с хендлом и страничкой (BEG and END can be used to only insert a subpart of the body. They count bytes from the beginning of the body). Получается как-то нелогично немного. Чтобы правильно проставить BEG и END, надо разбить data-buffer на заголовки и сам HTML, искать title в нем, найти начало конец и передавать в url-insert. Как-то так себе. В общем, можно переписать на более низком уровне (топикстартеру: по примененным функциям см. Emacs MIME).

(defun org-link-describe (url &optional descr)
  (let*  ((handle (with-current-buffer (url-retrieve-synchronously url)
		   (mm-dissect-buffer t)))
	  (charset (mail-content-type-get (mm-handle-type handle) 'charset))
	  (title (with-current-buffer (mm-handle-buffer handle)
		   (goto-char (point-min))
		   (re-search-forward "<title>\\([^<]+?\\)</title>")
		   (match-string-no-properties 1))))
    (mm-destroy-parts handle)
    (unless charset
      (setq charset "utf-8"))
    (mm-decode-string title (mm-charset-to-coding-system charset))))

Можно вместо подстановки «utf-8» по умолчанию еще сделать выпарсивание <meta ... charset=...>. Я этого не сделал.

Есть еще вариант - w3m-retrieve. Так как это браузер, то он по идее должен все перекодировать и по заголовку и по <meta>. Только я еще не проверил. Может, и не получится. Но w3m — это в любом случае overhead для получения только заголовка.

Zubok ★★★★★ ()
Последнее исправление: Zubok (всего исправлений: 1)
Ответ на: комментарий от openvg

https://rakuten.co.jp/ возвращает redirect на https://www.rakuten.co.jp/, поэтому там нет charset. Но у второй ссылки есть charset.

И у тебя во втором случае возвращается нормальный японский заголовок? У меня в обоих случаях вставляется как будто однобайтный.

Zubok ★★★★★ ()
Ответ на: комментарий от Zubok

Да, для этих двух ссылок у меня возвращается это:

«【楽天市場】Shopping is Entertainment! : インターネット最大級の通信販売、通販オンラインショッピングコミュニティ»

openvg ()
Ответ на: комментарий от openvg

Да, для этих двух ссылок у меня возвращается это:

У меня нет. Без принудительной установки в multibyte-buffer или charset в utf-8, у меня не преобразуется. Ничего не понимаю. Перепроверил кучу сайтов: грузинские, тайские, китайских пару, однобайтные koi8-r - все нормально. А этот - нет.

Уже этот абзац написал, а потом дошло. Ты же используешь теперь url-insert-file-contents. А эта функция преобразует в multibyte при вставке. То есть она рассматривает страницу как utf-8. Первый-то вариант с url-insert так не делает. Если нет кодировки, то он не преобразует. Или у тебя и с url-insert так работает?

Тогда понятно. Смотри: если он не увидел charset, он применяет эвристику. Вряд ли он парсит html на предмет <meta>. И docstring об этом же говорит. Эту функцию и использует url-insert-file-contents:

(defun url-insert-buffer-contents (buffer url &optional visit beg end replace)
  "Insert the contents of BUFFER into current buffer.
This is like `url-insert', but also decodes the current buffer as
if it had been inserted from a file named URL."
  (if visit (setq buffer-file-name url))
  (save-excursion
    (let ((start (point))
          (size-and-charset (url-insert buffer beg end)))
      (kill-buffer buffer)
      (when replace
        (delete-region (point-min) start)
        (delete-region (point) (point-max)))
      (unless (cadr size-and-charset)
        ;; If the headers don't specify any particular charset, use the
        ;; usual heuristic/rules that we apply to files.
        (decode-coding-inserted-region (point-min) (point) url
                                       visit beg end replace))
      (let ((inserted (car size-and-charset)))
        (list url (or (after-insert-file-set-coding inserted visit)
                      inserted))))))
Zubok ★★★★★ ()
Последнее исправление: Zubok (всего исправлений: 1)
Ответ на: комментарий от openvg

да, именно из-за этой эвристики я и начал использовать url-insert-file-contents

Ну, эвристика - вещь не очень красивая. В данном случае работает, но может не угадать. С однобайтными может не угадать. Я бы, конечно, делал процедуру, что сначала смотрел HTTP headers, а если там нет, то на <meta>, так как HTTP имеет больший приоритет. Ну а потом можно либо на умолчание уходить либо эвристику уже пробовать. У японцев в HTTP все же нет кодировки.

https://www.w3.org/International/questions/qa-html-encoding-declarations

If you have access to the server settings, you should also consider whether it makes sense to use the HTTP header. Note however that, since the HTTP header has a higher precedence than the in-document meta declarations, content authors should always take into account whether the character encoding is already declared in the HTTP header. If it is, the meta element must be set to declare the same encoding.

You can detect any encodings sent by the HTTP header using the Internationalization Checker.

Да, кстати, Internationalization Checker тоже говорит, что в HTTP заголовках у япошек нет кодировки, есть в <meta> внутри страницы:

http://validator.w3.org/i18n-checker/check?uri=https://www.rakuten.co.jp#vali...

http://validator.w3.org/i18n-checker/check?uri=https://rakuten.co.jp#validate...

HTTP Content-Type 	No encoding information found 	Content-Type: text/html
Meta tag 	         utf-8 	                        <meta charset=«utf-8»/>

Вот именно поэтому не было преобразования.

Zubok ★★★★★ ()
Последнее исправление: Zubok (всего исправлений: 1)
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.