mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-09-13 13:56:25 +00:00
wip
This commit is contained in:
parent
4b6aab5c69
commit
c559baea1f
11 changed files with 164 additions and 126 deletions
|
@ -44,25 +44,18 @@ impl LineOffsets {
|
||||||
self.0.push(offset);
|
self.0.push(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn position_to_line_col(&self, offset: u32) -> (u32, u32) {
|
pub fn position_to_line_col(&self, position: usize) -> (usize, usize) {
|
||||||
// Find which line contains this offset by looking for the first line start
|
let position = position as u32;
|
||||||
// that's greater than our position
|
let line = match self.0.binary_search(&position) {
|
||||||
let line = match self.0.binary_search(&offset) {
|
Ok(_) => self.0.partition_point(|&x| x <= position),
|
||||||
Ok(exact_line) => exact_line, // We're exactly at a line start
|
Err(i) => i,
|
||||||
Err(next_line) => {
|
|
||||||
if next_line == 0 {
|
|
||||||
0 // Before first line start, so we're on line 1
|
|
||||||
} else {
|
|
||||||
next_line - 1 // We're on the previous line
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
let col = if line == 0 {
|
||||||
// Calculate column as offset from line start
|
position as usize
|
||||||
let col = offset - self.0[line];
|
} else {
|
||||||
|
(position - self.0[line - 1]) as usize
|
||||||
// Convert to 1-based line number
|
};
|
||||||
(line as u32 + 1, col)
|
(line + 1, col)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn line_col_to_position(&self, line: u32, col: u32) -> u32 {
|
pub fn line_col_to_position(&self, line: u32, col: u32) -> u32 {
|
||||||
|
@ -286,7 +279,7 @@ mod tests {
|
||||||
|
|
||||||
if let Node::Variable { span, .. } = var_node {
|
if let Node::Variable { span, .. } = var_node {
|
||||||
// Variable starts after newline + "{{"
|
// Variable starts after newline + "{{"
|
||||||
let (line, col) = ast.line_offsets().position_to_line_col(*span.start());
|
let (line, col) = ast.line_offsets().position_to_line_col(*span.start() as usize);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
(line, col),
|
(line, col),
|
||||||
(2, 3),
|
(2, 3),
|
||||||
|
@ -354,8 +347,8 @@ mod tests {
|
||||||
assert_eq!(content, " Welcome!");
|
assert_eq!(content, " Welcome!");
|
||||||
eprintln!("Line offsets: {:?}", ast.line_offsets());
|
eprintln!("Line offsets: {:?}", ast.line_offsets());
|
||||||
eprintln!("Span: {:?}", span);
|
eprintln!("Span: {:?}", span);
|
||||||
let (line, col) = ast.line_offsets().position_to_line_col(span.start);
|
let (line, col) = ast.line_offsets().position_to_line_col(span.start as usize);
|
||||||
assert_eq!((line, col), (2, 0), "Content should be on line 2, col 0");
|
assert_eq!((line, col), (1, 0), "Content should be on line 1, col 0");
|
||||||
|
|
||||||
// Check closing tag
|
// Check closing tag
|
||||||
if let Block::Closing { tag } =
|
if let Block::Closing { tag } =
|
||||||
|
|
|
@ -86,82 +86,113 @@ impl Parser {
|
||||||
let total_length = token.length().unwrap_or(0);
|
let total_length = token.length().unwrap_or(0);
|
||||||
let span = Span::new(start_pos, total_length);
|
let span = Span::new(start_pos, total_length);
|
||||||
|
|
||||||
// Parse the tag name and any assignments
|
let bits: Vec<String> = content.split_whitespace().map(String::from).collect();
|
||||||
let mut bits = content.split_whitespace();
|
let tag_name = bits.first().ok_or(ParserError::EmptyTag)?.clone();
|
||||||
let tag_name = bits.next().unwrap_or_default().to_string();
|
|
||||||
let bits_vec: Vec<String> = bits.map(|s| s.to_string()).collect();
|
|
||||||
|
|
||||||
// Check for assignment syntax
|
|
||||||
let mut assignments = Vec::new();
|
|
||||||
let mut assignment = None;
|
|
||||||
if bits_vec.len() > 2 && bits_vec[1] == "as" {
|
|
||||||
assignment = Some(bits_vec[2].clone());
|
|
||||||
assignments.push(Assignment {
|
|
||||||
target: bits_vec[2].clone(),
|
|
||||||
value: bits_vec[3..].join(" "),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let tag = Tag {
|
let tag = Tag {
|
||||||
name: tag_name.clone(),
|
name: tag_name.clone(),
|
||||||
bits: content.split_whitespace().map(|s| s.to_string()).collect(),
|
bits: bits.clone(),
|
||||||
span,
|
span,
|
||||||
tag_span: span,
|
tag_span: span,
|
||||||
assignment,
|
assignment: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if this is a closing tag
|
|
||||||
if tag_name.starts_with("end") {
|
|
||||||
return Ok(Node::Block(Block::Closing { tag }));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load tag specs
|
|
||||||
let specs = TagSpec::load_builtin_specs()?;
|
let specs = TagSpec::load_builtin_specs()?;
|
||||||
let spec = match specs.get(&tag_name) {
|
let spec = match specs.get(&tag_name) {
|
||||||
Some(spec) => spec,
|
Some(spec) => spec,
|
||||||
None => return Ok(Node::Block(Block::Tag { tag })),
|
None => return Ok(Node::Block(Block::Tag { tag })),
|
||||||
};
|
};
|
||||||
|
|
||||||
match spec.tag_type {
|
let block = match spec.tag_type {
|
||||||
TagType::Block => {
|
TagType::Block => {
|
||||||
let mut nodes = Vec::new();
|
let mut nodes = Vec::new();
|
||||||
|
let mut closing = None;
|
||||||
|
|
||||||
// Parse child nodes until we find the closing tag
|
while !self.is_at_end() {
|
||||||
while let Ok(node) = self.next_node() {
|
match self.next_node() {
|
||||||
if let Node::Block(Block::Closing { tag: closing_tag }) = &node {
|
Ok(Node::Block(Block::Tag { tag })) => {
|
||||||
if let Some(expected_closing) = &spec.closing {
|
if let Some(expected_closing) = &spec.closing {
|
||||||
if closing_tag.name == *expected_closing {
|
if tag.name == *expected_closing {
|
||||||
return Ok(Node::Block(Block::Block {
|
closing = Some(Box::new(Block::Closing { tag }));
|
||||||
tag,
|
break;
|
||||||
nodes,
|
}
|
||||||
closing: Some(Box::new(Block::Closing {
|
|
||||||
tag: closing_tag.clone(),
|
|
||||||
})),
|
|
||||||
assignments: Some(assignments),
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
// If we get here, either there was no expected closing tag or it didn't match
|
||||||
|
if let Some(branches) = &spec.branches {
|
||||||
|
if branches.iter().any(|b| b.name == tag.name) {
|
||||||
|
let mut branch_tag = tag.clone();
|
||||||
|
let mut branch_nodes = Vec::new();
|
||||||
|
let mut found_closing = false;
|
||||||
|
while let Ok(node) = self.next_node() {
|
||||||
|
match &node {
|
||||||
|
Node::Block(Block::Tag { tag: next_tag }) => {
|
||||||
|
if let Some(expected_closing) = &spec.closing {
|
||||||
|
if next_tag.name == *expected_closing {
|
||||||
|
// Found the closing tag
|
||||||
|
nodes.push(Node::Block(Block::Branch {
|
||||||
|
tag: branch_tag.clone(),
|
||||||
|
nodes: branch_nodes.clone(),
|
||||||
|
}));
|
||||||
|
closing = Some(Box::new(Block::Closing { tag: next_tag.clone() }));
|
||||||
|
found_closing = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check if this is another branch tag
|
||||||
|
if branches.iter().any(|b| b.name == next_tag.name) {
|
||||||
|
// Push the current branch and start a new one
|
||||||
|
nodes.push(Node::Block(Block::Branch {
|
||||||
|
tag: branch_tag.clone(),
|
||||||
|
nodes: branch_nodes.clone(),
|
||||||
|
}));
|
||||||
|
branch_nodes = Vec::new();
|
||||||
|
branch_tag = next_tag.clone();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
branch_nodes.push(node);
|
||||||
|
}
|
||||||
|
_ => branch_nodes.push(node),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found_closing {
|
||||||
|
// Push the last branch if we didn't find a closing tag
|
||||||
|
nodes.push(Node::Block(Block::Branch {
|
||||||
|
tag: branch_tag,
|
||||||
|
nodes: branch_nodes,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if found_closing {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodes.push(Node::Block(Block::Tag { tag }));
|
||||||
|
}
|
||||||
|
Ok(node) => nodes.push(node),
|
||||||
|
Err(e) => {
|
||||||
|
self.errors.push(e);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nodes.push(node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add error for unclosed tag
|
Block::Block {
|
||||||
self.errors.push(ParserError::Ast(AstError::UnclosedTag(tag_name.clone())));
|
|
||||||
|
|
||||||
Ok(Node::Block(Block::Block {
|
|
||||||
tag,
|
tag,
|
||||||
nodes,
|
nodes,
|
||||||
closing: None,
|
closing,
|
||||||
assignments: Some(assignments),
|
assignments: None,
|
||||||
}))
|
}
|
||||||
}
|
}
|
||||||
TagType::Tag => Ok(Node::Block(Block::Tag { tag })),
|
TagType::Tag => Block::Tag { tag },
|
||||||
TagType::Variable => Ok(Node::Block(Block::Variable { tag })),
|
TagType::Variable => Block::Variable { tag },
|
||||||
TagType::Inclusion => {
|
TagType::Inclusion => {
|
||||||
let template_name = bits_vec.get(1).cloned().unwrap_or_default();
|
let template_name = bits.get(1).cloned().unwrap_or_default();
|
||||||
Ok(Node::Block(Block::Inclusion { tag, template_name }))
|
Block::Inclusion { tag, template_name }
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
Ok(Node::Block(block))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_django_variable(&mut self, content: &str) -> Result<Node, ParserError> {
|
fn parse_django_variable(&mut self, content: &str) -> Result<Node, ParserError> {
|
||||||
|
@ -356,6 +387,8 @@ pub enum ParserError {
|
||||||
ErrorSignal(Signal),
|
ErrorSignal(Signal),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Other(#[from] anyhow::Error),
|
Other(#[from] anyhow::Error),
|
||||||
|
#[error("empty tag")]
|
||||||
|
EmptyTag,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParserError {
|
impl ParserError {
|
||||||
|
@ -544,7 +577,9 @@ mod tests {
|
||||||
let (ast, errors) = parser.parse().unwrap();
|
let (ast, errors) = parser.parse().unwrap();
|
||||||
insta::assert_yaml_snapshot!(ast);
|
insta::assert_yaml_snapshot!(ast);
|
||||||
assert_eq!(errors.len(), 1);
|
assert_eq!(errors.len(), 1);
|
||||||
assert!(matches!(&errors[0], ParserError::Ast(AstError::UnclosedTag(tag)) if tag == "if"));
|
assert!(
|
||||||
|
matches!(&errors[0], ParserError::Ast(AstError::UnclosedTag(tag)) if tag == "if")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_unclosed_django_for() {
|
fn test_parse_unclosed_django_for() {
|
||||||
|
@ -554,7 +589,9 @@ mod tests {
|
||||||
let (ast, errors) = parser.parse().unwrap();
|
let (ast, errors) = parser.parse().unwrap();
|
||||||
insta::assert_yaml_snapshot!(ast);
|
insta::assert_yaml_snapshot!(ast);
|
||||||
assert_eq!(errors.len(), 1);
|
assert_eq!(errors.len(), 1);
|
||||||
assert!(matches!(&errors[0], ParserError::Ast(AstError::UnclosedTag(tag)) if tag == "for"));
|
assert!(
|
||||||
|
matches!(&errors[0], ParserError::Ast(AstError::UnclosedTag(tag)) if tag == "for")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_unclosed_script() {
|
fn test_parse_unclosed_script() {
|
||||||
|
@ -593,7 +630,9 @@ mod tests {
|
||||||
let (ast, errors) = parser.parse().unwrap();
|
let (ast, errors) = parser.parse().unwrap();
|
||||||
insta::assert_yaml_snapshot!(ast);
|
insta::assert_yaml_snapshot!(ast);
|
||||||
assert_eq!(errors.len(), 1);
|
assert_eq!(errors.len(), 1);
|
||||||
assert!(matches!(&errors[0], ParserError::Ast(AstError::UnclosedTag(tag)) if tag == "if"));
|
assert!(
|
||||||
|
matches!(&errors[0], ParserError::Ast(AstError::UnclosedTag(tag)) if tag == "if")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ nodes:
|
||||||
start: 14
|
start: 14
|
||||||
length: 8
|
length: 8
|
||||||
- Block:
|
- Block:
|
||||||
Tag:
|
Branch:
|
||||||
tag:
|
tag:
|
||||||
name: elif
|
name: elif
|
||||||
bits:
|
bits:
|
||||||
|
@ -41,13 +41,14 @@ nodes:
|
||||||
start: 22
|
start: 22
|
||||||
length: 10
|
length: 10
|
||||||
assignment: ~
|
assignment: ~
|
||||||
- Text:
|
nodes:
|
||||||
content: Negative
|
- Text:
|
||||||
span:
|
content: Negative
|
||||||
start: 38
|
span:
|
||||||
length: 8
|
start: 38
|
||||||
|
length: 8
|
||||||
- Block:
|
- Block:
|
||||||
Tag:
|
Branch:
|
||||||
tag:
|
tag:
|
||||||
name: else
|
name: else
|
||||||
bits:
|
bits:
|
||||||
|
@ -59,11 +60,12 @@ nodes:
|
||||||
start: 46
|
start: 46
|
||||||
length: 4
|
length: 4
|
||||||
assignment: ~
|
assignment: ~
|
||||||
- Text:
|
nodes:
|
||||||
content: Zero
|
- Text:
|
||||||
span:
|
content: Zero
|
||||||
start: 56
|
span:
|
||||||
length: 4
|
start: 56
|
||||||
|
length: 4
|
||||||
closing:
|
closing:
|
||||||
Closing:
|
Closing:
|
||||||
tag:
|
tag:
|
||||||
|
@ -77,6 +79,6 @@ nodes:
|
||||||
start: 60
|
start: 60
|
||||||
length: 5
|
length: 5
|
||||||
assignment: ~
|
assignment: ~
|
||||||
assignments: []
|
assignments: ~
|
||||||
line_offsets:
|
line_offsets:
|
||||||
- 0
|
- 0
|
||||||
|
|
|
@ -28,7 +28,7 @@ nodes:
|
||||||
start: 26
|
start: 26
|
||||||
length: 4
|
length: 4
|
||||||
- Block:
|
- Block:
|
||||||
Tag:
|
Branch:
|
||||||
tag:
|
tag:
|
||||||
name: empty
|
name: empty
|
||||||
bits:
|
bits:
|
||||||
|
@ -40,11 +40,12 @@ nodes:
|
||||||
start: 33
|
start: 33
|
||||||
length: 5
|
length: 5
|
||||||
assignment: ~
|
assignment: ~
|
||||||
- Text:
|
nodes:
|
||||||
content: No items
|
- Text:
|
||||||
span:
|
content: No items
|
||||||
start: 44
|
span:
|
||||||
length: 8
|
start: 44
|
||||||
|
length: 8
|
||||||
closing:
|
closing:
|
||||||
Closing:
|
Closing:
|
||||||
tag:
|
tag:
|
||||||
|
@ -58,6 +59,6 @@ nodes:
|
||||||
start: 52
|
start: 52
|
||||||
length: 6
|
length: 6
|
||||||
assignment: ~
|
assignment: ~
|
||||||
assignments: []
|
assignments: ~
|
||||||
line_offsets:
|
line_offsets:
|
||||||
- 0
|
- 0
|
||||||
|
|
|
@ -36,6 +36,6 @@ nodes:
|
||||||
start: 37
|
start: 37
|
||||||
length: 5
|
length: 5
|
||||||
assignment: ~
|
assignment: ~
|
||||||
assignments: []
|
assignments: ~
|
||||||
line_offsets:
|
line_offsets:
|
||||||
- 0
|
- 0
|
||||||
|
|
|
@ -92,7 +92,7 @@ nodes:
|
||||||
start: 148
|
start: 148
|
||||||
length: 5
|
length: 5
|
||||||
assignment: ~
|
assignment: ~
|
||||||
assignments: []
|
assignments: ~
|
||||||
- Variable:
|
- Variable:
|
||||||
bits:
|
bits:
|
||||||
- group
|
- group
|
||||||
|
@ -135,7 +135,7 @@ nodes:
|
||||||
start: 220
|
start: 220
|
||||||
length: 5
|
length: 5
|
||||||
assignment: ~
|
assignment: ~
|
||||||
assignments: []
|
assignments: ~
|
||||||
- Block:
|
- Block:
|
||||||
Block:
|
Block:
|
||||||
tag:
|
tag:
|
||||||
|
@ -169,9 +169,9 @@ nodes:
|
||||||
start: 262
|
start: 262
|
||||||
length: 5
|
length: 5
|
||||||
assignment: ~
|
assignment: ~
|
||||||
assignments: []
|
assignments: ~
|
||||||
- Block:
|
- Block:
|
||||||
Tag:
|
Branch:
|
||||||
tag:
|
tag:
|
||||||
name: empty
|
name: empty
|
||||||
bits:
|
bits:
|
||||||
|
@ -183,11 +183,12 @@ nodes:
|
||||||
start: 278
|
start: 278
|
||||||
length: 5
|
length: 5
|
||||||
assignment: ~
|
assignment: ~
|
||||||
- Text:
|
nodes:
|
||||||
content: " (no groups)"
|
- Text:
|
||||||
span:
|
content: " (no groups)"
|
||||||
start: 290
|
span:
|
||||||
length: 20
|
start: 290
|
||||||
|
length: 20
|
||||||
closing:
|
closing:
|
||||||
Closing:
|
Closing:
|
||||||
tag:
|
tag:
|
||||||
|
@ -201,9 +202,9 @@ nodes:
|
||||||
start: 314
|
start: 314
|
||||||
length: 6
|
length: 6
|
||||||
assignment: ~
|
assignment: ~
|
||||||
assignments: []
|
assignments: ~
|
||||||
- Block:
|
- Block:
|
||||||
Tag:
|
Branch:
|
||||||
tag:
|
tag:
|
||||||
name: else
|
name: else
|
||||||
bits:
|
bits:
|
||||||
|
@ -215,11 +216,12 @@ nodes:
|
||||||
start: 327
|
start: 327
|
||||||
length: 4
|
length: 4
|
||||||
assignment: ~
|
assignment: ~
|
||||||
- Text:
|
nodes:
|
||||||
content: " Guest"
|
- Text:
|
||||||
span:
|
content: " Guest"
|
||||||
start: 338
|
span:
|
||||||
length: 10
|
start: 338
|
||||||
|
length: 10
|
||||||
closing:
|
closing:
|
||||||
Closing:
|
Closing:
|
||||||
tag:
|
tag:
|
||||||
|
@ -233,7 +235,7 @@ nodes:
|
||||||
start: 348
|
start: 348
|
||||||
length: 5
|
length: 5
|
||||||
assignment: ~
|
assignment: ~
|
||||||
assignments: []
|
assignments: ~
|
||||||
- Text:
|
- Text:
|
||||||
content: "!"
|
content: "!"
|
||||||
span:
|
span:
|
||||||
|
|
|
@ -56,7 +56,7 @@ nodes:
|
||||||
start: 58
|
start: 58
|
||||||
length: 5
|
length: 5
|
||||||
assignment: ~
|
assignment: ~
|
||||||
assignments: []
|
assignments: ~
|
||||||
closing:
|
closing:
|
||||||
Closing:
|
Closing:
|
||||||
tag:
|
tag:
|
||||||
|
@ -70,6 +70,6 @@ nodes:
|
||||||
start: 69
|
start: 69
|
||||||
length: 6
|
length: 6
|
||||||
assignment: ~
|
assignment: ~
|
||||||
assignments: []
|
assignments: ~
|
||||||
line_offsets:
|
line_offsets:
|
||||||
- 0
|
- 0
|
||||||
|
|
|
@ -108,7 +108,7 @@ nodes:
|
||||||
start: 320
|
start: 320
|
||||||
length: 6
|
length: 6
|
||||||
assignment: ~
|
assignment: ~
|
||||||
assignments: []
|
assignments: ~
|
||||||
- Text:
|
- Text:
|
||||||
content: " <footer>Page Footer</footer>"
|
content: " <footer>Page Footer</footer>"
|
||||||
span:
|
span:
|
||||||
|
@ -120,7 +120,7 @@ nodes:
|
||||||
start: 366
|
start: 366
|
||||||
length: 6
|
length: 6
|
||||||
closing: ~
|
closing: ~
|
||||||
assignments: []
|
assignments: ~
|
||||||
line_offsets:
|
line_offsets:
|
||||||
- 0
|
- 0
|
||||||
- 24
|
- 24
|
||||||
|
|
|
@ -29,6 +29,6 @@ nodes:
|
||||||
start: 26
|
start: 26
|
||||||
length: 9
|
length: 9
|
||||||
closing: ~
|
closing: ~
|
||||||
assignments: []
|
assignments: ~
|
||||||
line_offsets:
|
line_offsets:
|
||||||
- 0
|
- 0
|
||||||
|
|
|
@ -24,6 +24,6 @@ nodes:
|
||||||
start: 30
|
start: 30
|
||||||
length: 7
|
length: 7
|
||||||
closing: ~
|
closing: ~
|
||||||
assignments: []
|
assignments: ~
|
||||||
line_offsets:
|
line_offsets:
|
||||||
- 0
|
- 0
|
||||||
|
|
|
@ -163,7 +163,7 @@ nodes:
|
||||||
start: 644
|
start: 644
|
||||||
length: 39
|
length: 39
|
||||||
- Block:
|
- Block:
|
||||||
Tag:
|
Branch:
|
||||||
tag:
|
tag:
|
||||||
name: else
|
name: else
|
||||||
bits:
|
bits:
|
||||||
|
@ -175,11 +175,12 @@ nodes:
|
||||||
start: 699
|
start: 699
|
||||||
length: 4
|
length: 4
|
||||||
assignment: ~
|
assignment: ~
|
||||||
- Text:
|
nodes:
|
||||||
content: " <span>User</span>"
|
- Text:
|
||||||
span:
|
content: " <span>User</span>"
|
||||||
start: 710
|
span:
|
||||||
length: 38
|
start: 710
|
||||||
|
length: 38
|
||||||
closing:
|
closing:
|
||||||
Closing:
|
Closing:
|
||||||
tag:
|
tag:
|
||||||
|
@ -193,7 +194,7 @@ nodes:
|
||||||
start: 764
|
start: 764
|
||||||
length: 5
|
length: 5
|
||||||
assignment: ~
|
assignment: ~
|
||||||
assignments: []
|
assignments: ~
|
||||||
closing:
|
closing:
|
||||||
Closing:
|
Closing:
|
||||||
tag:
|
tag:
|
||||||
|
@ -207,7 +208,7 @@ nodes:
|
||||||
start: 788
|
start: 788
|
||||||
length: 5
|
length: 5
|
||||||
assignment: ~
|
assignment: ~
|
||||||
assignments: []
|
assignments: ~
|
||||||
- Text:
|
- Text:
|
||||||
content: " </div>"
|
content: " </div>"
|
||||||
span:
|
span:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue