Read hyperlink instr (#518)

* fix: read hyperlink instr

* fix

* fix

* fix

* rc7
main
bokuweb 2022-08-10 14:45:27 +09:00 committed by GitHub
parent 0f5e1d1cdc
commit fec94fd5f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 2054 additions and 44 deletions

View File

@ -4,7 +4,9 @@ use crate::documents::*;
use crate::types::*; use crate::types::*;
use crate::xml_builder::*; use crate::xml_builder::*;
#[derive(Serialize, Debug, Clone, PartialEq)] #[derive(Serialize, Debug, Clone, PartialEq, ts_rs::TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct FieldChar { pub struct FieldChar {
pub field_char_type: FieldCharType, pub field_char_type: FieldCharType,
pub dirty: bool, pub dirty: bool,

View File

@ -0,0 +1,68 @@
use serde::Serialize;
// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_HYPERLINKHYPERLINK_topic_ID0EFYG1.html
#[derive(Serialize, Debug, Clone, PartialEq, Default, ts_rs::TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct InstrHyperlink {
pub target: String,
// \l
pub anchor: bool,
}
impl InstrHyperlink {
pub fn new(target: impl Into<String>) -> Self {
Self {
target: target.into(),
..Default::default()
}
}
}
// impl BuildXML for instrHyperlink {
// fn build(&self) -> Vec<u8> {
// TODO:
// }
// }
impl std::str::FromStr for InstrHyperlink {
type Err = ();
fn from_str(instr: &str) -> Result<Self, Self::Err> {
let mut s = instr.split(' ');
let mut target = "".to_string();
let mut anchor = false;
loop {
if let Some(i) = s.next() {
match i {
"\\l" => {
anchor = true;
}
"\\m" => {
// TODO:
}
"\\n" => {
// TODO:
}
"\\o" => {
// TODO: Support later
let _ = s.next();
}
"\\t" => {
// TODO: Support later
let _ = s.next();
}
_ => {
target = i.replace("&quot;", "").replace("\"", "").to_string();
}
}
} else {
// FIXME: For now, return error if target is not found
if target.is_empty() {
return Err(());
}
return Ok(Self { target, anchor });
}
}
}
}

View File

@ -4,6 +4,7 @@ use crate::documents::*;
// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_PAGEREFPAGEREF_topic_ID0EHXK1.html // https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_PAGEREFPAGEREF_topic_ID0EHXK1.html
#[derive(Serialize, Debug, Clone, PartialEq, Default)] #[derive(Serialize, Debug, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct InstrPAGEREF { pub struct InstrPAGEREF {
pub page_ref: String, pub page_ref: String,
pub hyperlink: bool, pub hyperlink: bool,

View File

@ -4,6 +4,7 @@ use crate::documents::*;
// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_TCTC_topic_ID0EU2N1.html // https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_TCTC_topic_ID0EU2N1.html
#[derive(Serialize, Debug, Clone, PartialEq, Default)] #[derive(Serialize, Debug, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct InstrTC { pub struct InstrTC {
pub text: String, pub text: String,
// \n Omits the page number for the entry. // \n Omits the page number for the entry.

View File

@ -9,6 +9,7 @@ pub enum InstrText {
TOC(InstrToC), TOC(InstrToC),
TC(InstrTC), TC(InstrTC),
PAGEREF(InstrPAGEREF), PAGEREF(InstrPAGEREF),
HYPERLINK(InstrHyperlink),
Unsupported(String), Unsupported(String),
} }
@ -18,6 +19,7 @@ impl BuildXML for Box<InstrText> {
InstrText::TOC(toc) => toc.build(), InstrText::TOC(toc) => toc.build(),
InstrText::TC(tc) => tc.build(), InstrText::TC(tc) => tc.build(),
InstrText::PAGEREF(page_ref) => page_ref.build(), InstrText::PAGEREF(page_ref) => page_ref.build(),
InstrText::HYPERLINK(_link) => todo!(),
InstrText::Unsupported(s) => s.as_bytes().to_vec(), InstrText::Unsupported(s) => s.as_bytes().to_vec(),
}; };
XMLBuilder::new() XMLBuilder::new()
@ -52,6 +54,12 @@ impl Serialize for InstrText {
t.serialize_field("data", s)?; t.serialize_field("data", s)?;
t.end() t.end()
} }
InstrText::HYPERLINK(ref s) => {
let mut t = serializer.serialize_struct("HYPERLINK", 2)?;
t.serialize_field("type", "hyperlink")?;
t.serialize_field("data", s)?;
t.end()
}
InstrText::Unsupported(ref s) => { InstrText::Unsupported(ref s) => {
let mut t = serializer.serialize_struct("Unsupported", 2)?; let mut t = serializer.serialize_struct("Unsupported", 2)?;
t.serialize_field("type", "unsupported")?; t.serialize_field("type", "unsupported")?;

View File

@ -2,7 +2,8 @@ use serde::Serialize;
use crate::documents::*; use crate::documents::*;
#[derive(Serialize, Debug, Clone, PartialEq, Default)] #[derive(Serialize, Debug, Clone, PartialEq, Default, ts_rs::TS)]
#[ts(export)]
pub struct StyleWithLevel(pub (String, usize)); pub struct StyleWithLevel(pub (String, usize));
impl StyleWithLevel { impl StyleWithLevel {
@ -11,7 +12,9 @@ impl StyleWithLevel {
} }
} }
// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_TOCTOC_topic_ID0ELZO1.html // https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_TOCTOC_topic_ID0ELZO1.html
#[derive(Serialize, Debug, Clone, PartialEq, Default)] #[derive(Serialize, Debug, Clone, PartialEq, Default, ts_rs::TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct InstrToC { pub struct InstrToC {
// \o If no heading range is specified, all heading levels used in the document are listed. // \o If no heading range is specified, all heading levels used in the document are listed.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]

View File

@ -34,6 +34,7 @@ mod hyperlink;
mod indent; mod indent;
mod indent_level; mod indent_level;
mod insert; mod insert;
mod instr_hyperlink;
mod instr_pageref; mod instr_pageref;
mod instr_tc; mod instr_tc;
mod instr_text; mod instr_text;
@ -148,6 +149,7 @@ pub use hyperlink::*;
pub use indent::*; pub use indent::*;
pub use indent_level::*; pub use indent_level::*;
pub use insert::*; pub use insert::*;
pub use instr_hyperlink::*;
pub use instr_pageref::*; pub use instr_pageref::*;
pub use instr_tc::*; pub use instr_tc::*;
pub use instr_text::*; pub use instr_text::*;

View File

@ -38,6 +38,11 @@ impl ElementReader for InstrText {
return Ok(InstrText::TC(instr)); return Ok(InstrText::TC(instr));
} }
} }
if instr.starts_with("HYPERLINK") {
if let Ok(instr) = InstrHyperlink::from_str(instr) {
return Ok(InstrText::HYPERLINK(instr));
}
}
if instr.starts_with("PAGEREF") { if instr.starts_with("PAGEREF") {
if let Ok(instr) = InstrPAGEREF::from_str(instr) { if let Ok(instr) = InstrPAGEREF::from_str(instr) {
return Ok(InstrText::PAGEREF(instr)); return Ok(InstrText::PAGEREF(instr));

View File

@ -10,7 +10,9 @@ use wasm_bindgen::prelude::*;
use super::errors; use super::errors;
#[wasm_bindgen] #[wasm_bindgen]
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq)] #[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub enum FieldCharType { pub enum FieldCharType {
Begin, Begin,
Separate, Separate,

View File

@ -0,0 +1,3 @@
import type { FieldCharType } from "./FieldCharType";
export interface FieldChar { fieldCharType: FieldCharType, dirty: boolean, }

View File

@ -0,0 +1,2 @@
export type FieldCharType = "begin" | "separate" | "end" | "unsupported";

View File

@ -0,0 +1,2 @@
export interface InstrHyperlink { target: string, anchor: boolean, }

View File

@ -0,0 +1,3 @@
import type { StyleWithLevel } from "./StyleWithLevel";
export interface InstrToC { headingStylesRange?: [number, number], tcFieldLevelRange?: [number, number], omitPageNumbersLevelRange?: [number, number], entryBookmarkName?: string, stylesWithLevels: Array<StyleWithLevel>, entryAndPageNumberSeparator?: string, sequenceAndPageNumbersSeparator?: string, captionLabel: string | null, captionLabelIncludingNumbers?: string, seqFieldIdentifierForPrefix?: string, tcFieldIdentifier?: string, hyperlink: boolean, preserveTab: boolean, preserveNewLine: boolean, useAppliedParagraphLineLevel: boolean, hideTabAndPageNumbersInWebview: boolean, }

View File

@ -0,0 +1,2 @@
export type StyleWithLevel = [string, number];

View File

@ -4,6 +4,9 @@ import { CommentRangeStartJSON, CommentRangeEndJSON } from "..";
import { BorderType } from "../border"; import { BorderType } from "../border";
import { InsertJSON, DeleteJSON } from "./paragraph"; import { InsertJSON, DeleteJSON } from "./paragraph";
import { VertAlignType } from "../run"; import { VertAlignType } from "../run";
import { FieldChar } from "./bindings/FieldChar";
import { InstrHyperlink } from "./bindings/InstrHyperlink";
import { InstrToC } from "./bindings/InstrToC";
export type TextBorderJSON = { export type TextBorderJSON = {
borderType: BorderType; borderType: BorderType;
@ -53,7 +56,9 @@ export type RunChildJSON =
| DrawingJSON | DrawingJSON
| ShapeJSON | ShapeJSON
| CommentRangeStartJSON | CommentRangeStartJSON
| CommentRangeEndJSON; | CommentRangeEndJSON
| FieldCharJSON
| InstrTextJSON;
export type TextJSON = { export type TextJSON = {
type: "text"; type: "text";
@ -89,3 +94,21 @@ export type RunJSON = {
children: RunChildJSON[]; children: RunChildJSON[];
}; };
}; };
export type FieldCharJSON = {
type: "fieldChar";
data: FieldChar;
};
export type InstrTextJSON = {
type: "instrText";
data:
| {
type: "hyperlink";
data: InstrHyperlink;
}
| {
type: "toc";
data: InstrToC;
};
};

View File

@ -1,6 +1,6 @@
{ {
"name": "docx-wasm", "name": "docx-wasm",
"version": "0.0.276-rc4", "version": "0.0.276-rc7",
"main": "dist/node/index.js", "main": "dist/node/index.js",
"browser": "dist/web/index.js", "browser": "dist/web/index.js",
"author": "bokuweb <bokuweb12@gmail.com>", "author": "bokuweb <bokuweb12@gmail.com>",

File diff suppressed because it is too large Load Diff

View File

@ -159,6 +159,12 @@ describe("reader", () => {
const json = w.readDocx(buffer); const json = w.readDocx(buffer);
expect(json).toMatchSnapshot(); expect(json).toMatchSnapshot();
}); });
test("should read hyperlink instr", () => {
const buffer = readFileSync("../fixtures/instr_links/instr_links.docx");
const json = w.readDocx(buffer);
expect(json).toMatchSnapshot();
});
}); });
describe("writer", () => { describe("writer", () => {

Binary file not shown.