mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-09 12:32:52 +00:00
A language tag can't end in a singleton
A singleton (one letter or digit) starts an extension, or with x private use, and BCP 47 needs a subtag after it. en-x and en-a were written as the document's language. They and a singleton straight after another are now left out; one-character subtags inside private use stay allowed.
This commit is contained in:
1 parent
0cae1c518b
commit
561cc0d707
2 files changed
+29
-6
No files matched your search
@@ -14,16 +14,33 @@ const LONGEST_TAG: usize = 255;
|
||||
|
||||
/// `lang` when it is a well formed language tag (BCP 47, as a PDF's `/Lang` holds one): a primary
|
||||
/// language of two or three letters, then subtags of one to eight letters or digits, joined by
|
||||
/// hyphens, [`LONGEST_TAG`] characters at most. Anything else names no language.
|
||||
/// hyphens, [`LONGEST_TAG`] characters at most. A singleton (one letter or digit) starts an
|
||||
/// extension, or with `x` private use, and needs a subtag after it. Anything else names no
|
||||
/// language.
|
||||
pub(crate) fn language_tag(lang: Option<&str>) -> Option<&str> {
|
||||
let tag = lang?.trim();
|
||||
let mut subtags = tag.split('-');
|
||||
let primary = subtags.next()?;
|
||||
let well_formed = tag.len() <= LONGEST_TAG
|
||||
&& (2..=3).contains(&primary.len())
|
||||
&& primary.bytes().all(|b| b.is_ascii_alphabetic())
|
||||
&& subtags.all(|s| (1..=8).contains(&s.len()) && s.bytes().all(|b| b.is_ascii_alphanumeric()));
|
||||
well_formed.then_some(tag)
|
||||
if tag.len() > LONGEST_TAG || !(2..=3).contains(&primary.len()) || !primary.bytes().all(|b| b.is_ascii_alphabetic()) {
|
||||
return None;
|
||||
}
|
||||
// In private use every subtag is data, one character long or not.
|
||||
let (mut private, mut singleton) = (false, false);
|
||||
for s in subtags {
|
||||
if !(1..=8).contains(&s.len()) || !s.bytes().all(|b| b.is_ascii_alphanumeric()) {
|
||||
return None;
|
||||
}
|
||||
if s.len() == 1 && !private {
|
||||
if singleton {
|
||||
return None;
|
||||
}
|
||||
private = s.eq_ignore_ascii_case("x");
|
||||
singleton = true;
|
||||
} else {
|
||||
singleton = false;
|
||||
}
|
||||
}
|
||||
(!singleton).then_some(tag)
|
||||
}
|
||||
|
||||
pub(crate) fn esc(s: &str) -> String {
|
||||
|
||||
@@ -265,6 +265,8 @@ fn word_and_html_take_the_documents_language() {
|
||||
assert!(styles(Some(long)).contains(&format!("<w:lang w:val=\"{long}\"/>")), "{long}");
|
||||
assert!(html_with_language(&pages, "Report", Some(long)).contains(&format!("<html lang=\"{long}\">")), "{long}");
|
||||
let too_long = format!("en{}", "-abcdefgh".repeat(29));
|
||||
// Private use takes one-character subtags.
|
||||
assert!(html_with_language(&pages, "Report", Some("en-x-a-b")).contains("<html lang=\"en-x-a-b\">"));
|
||||
for lang in [
|
||||
None,
|
||||
Some(""),
|
||||
@@ -274,6 +276,10 @@ fn word_and_html_take_the_documents_language() {
|
||||
Some("bulg-BG"),
|
||||
Some("bg--BG"),
|
||||
Some("bg-"),
|
||||
Some("en-x"),
|
||||
Some("en-a"),
|
||||
Some("en-a-x-private"),
|
||||
Some("en-US-u-ca-x"),
|
||||
Some("en-US-abcdefghi"),
|
||||
Some(too_long.as_str()),
|
||||
Some("en-\"x\""),
|
||||
|
||||
Reference in new issue
Block a user