我要如何给linq模板tag增加自定义属性或添加批注

我们的业务场景:

  1. 使用linq模板配置word<模板Template> 比如其中有tag1
  2. 使用<模板Template>生成<预览报表PreviewReport>,其中tag1对应值VALUE1,在<预览报表PreviewReport>中应该反应出VALUE1与tag1的对应关系,比如VALUE1处增加批注或者直接生成片段tag1_VALUE1.
  3. 用户修改生成的<预览报表PreviewReport>中的VALUE1为NEW_VALUE1,并重新上传
    4.系统解析第三步中用户上传的word,将数据源中tag1的值修改为NEW_VALUE1

请问aspose.words-java 是否有开箱即用的功能完成上述需求?
我应该如何为linq模板增加自定义xml属性或批注?

在编辑问题的过程中,我发现如果我输入"\<tag\>","\<<tag1>\>","\<VALUE1\>"等时,编辑完成后的问题不会显示对应文本。"\<"和"\>"是否经过某些特殊处理?我用符号\来保证他们完整显示。

@Doraemon 不幸的是,你的要求还不够明确。 如果可能的话,您能提供您的示例模板、中间文件和预期输出文件吗? 我们将检查它们并为您提供更多信息。

PS:不幸的是,中文不是我的语言之一,恐怕机器翻译无法提供有关您的要求的适当线索。

@alexey.noskov

Simple.zip (23.4 KB)

In the attachment, Template.docx is Template and Result.docx is my expectation.There is only simple linq template in the Template.docx, but there are value and comment in the Result.Docx.

My english is poor, I hope I didn’t offend you.

@alexey.noskov or can i get all linq template nodes in the word?

@Doraemon

Do I understand it correctly that adding of a comment using LINQ Reporting Engine is expected?

@ivan.lyagin

Although perhaps a more accurate description was that i want to customerize the LINQ Reporting engine rendering logic.

I want to tag the LINQ Template Nodes in the word (by a comment or by some cutomrize xml properties or anything else) and the tags will exist after rendering.

Here are our business steps:

  1. Staffs configure the report template and dataSources, then generate the report.
  2. Managers review the report.If there is something wrong in the report, managers or staffs will directly modify the report rather than the report template or the dataSources.
  3. Staffs upload the modified report(in the step 2) to our system. The dataSources related to the modifications ( in the step 2)will be updated.

So, we need to tag the template nodes and after rendering or directly modifing the tags exist

@Doraemon

There is no built-in functionality to achieve this. However, this can be done with some pre-processing of a template document on the fly before passing it to the engine as per the following code snippet:

// Open a real template document here instead.
DocumentBuilder builder = new DocumentBuilder();
Document doc = builder.Document;
builder.Write("text before <<[tag1]>> text between <<[tag2]>> text after");

// Modify the template to include comments.
//
// 1. Split runs of tag starts and add <<!>> marks for tag ends.
Regex tagPattern = new Regex("<<.*?>>");
doc.Range.Replace(tagPattern, "", new FindReplaceOptions(new ReplacingCallback1()));

// 2. Split runs of <<!>> marks.
doc.Range.Replace("<<!>>", "<<!>>");

// 3. Collect tag starts, ends, and texts. No replacing occurs here.
ReplacingCallback2 callback2 = new ReplacingCallback2();
doc.Range.Replace(tagPattern, "", new FindReplaceOptions(callback2));

// 4. Insert comments for tags.
for (int i = 0; i < callback2.TagStarts.Count; i++)
{
    Node tagStart = callback2.TagStarts[i];
    Node tagEnd = callback2.TagEnds[i];

    // Note, it is important to trim these characters here, because
    // tags in comments are also replaced with values otherwise.
    string tagText = callback2.TagTexts[i].TrimStart('<').TrimEnd('>');

    Comment comment = new Comment(doc);
    comment.Paragraphs.Add(new Paragraph(doc));
    comment.FirstParagraph.Runs.Add(new Run(doc, tagText));
                
    CommentRangeStart commentRangeStart = new CommentRangeStart(doc, comment.Id);
    CommentRangeEnd commentRangeEnd = new CommentRangeEnd(doc, comment.Id);

    tagStart.ParentNode.InsertBefore(commentRangeStart, tagStart);
    tagEnd.ParentNode.InsertBefore(commentRangeEnd, tagEnd);
    tagEnd.ParentNode.InsertBefore(comment, tagEnd);
}

// 5. Remove <<!>> marks.
doc.Range.Replace("<<!>>", "");

// Finally, use the modified template to build a report.
ReportingEngine engine = new ReportingEngine();
engine.BuildReport(doc, new object[] { "value1", "value2" }, new[] { "tag1", "tag2" });

doc.Save("Report.docx");

//-----------------------------------------------------------

internal class ReplacingCallback1 : IReplacingCallback
{
    public ReplaceAction Replacing(ReplacingArgs args)
    {
        args.Replacement = args.Match.Value + "<<!>>";
        return ReplaceAction.Replace;
    }
}

internal class ReplacingCallback2 : IReplacingCallback
{
    public ReplaceAction Replacing(ReplacingArgs args)
    {
        if (args.Match.Value != "<<!>>")
        {
            TagStarts.Add(args.MatchNode);
            TagTexts.Add(args.Match.Value);
        }
        else
            TagEnds.Add(args.MatchNode);

        // Simply collect, no need to replace anything.
        return ReplaceAction.Skip;
    }

    internal System.Collections.Generic.List<Node> TagStarts { get; } = new();
    internal System.Collections.Generic.List<Node> TagEnds { get; } = new();
    internal System.Collections.Generic.List<string> TagTexts { get; } = new();
}
1 Like

@ivan.lyagin
Thank you for your answer.

That’s what i want.

1 Like

@ivan.lyagin
Hi ivan, there is something wrong with words-java.

What is the equivalent to “args.Match.Value” in words-java?

I tried java.util.regex.Matcher.appendTail or com.aspose.words.ReplacingArgs.getMatchNode().getText(), but all of these didn’t work well.

If I use java.util.regex.Matcher.appendTail to get full original Text, ReplacingCallback2 hits two times, and does not match the tag end.

If i use com.aspose.words.ReplacingArgs.getMatchNode().getText(),ReplacingCallback2 just hit one time, but also does not match the tag end.

There are two versions for the ReplacingCallback1


Update:
I fix this question.
In the Callback1 , i use java.util.regex.Matcher.appendTail

  Matcher match = args.getMatch();
        StringBuilder sb = new StringBuilder(match.end() + 1 + value.length());
        match.appendTail(sb);
        sb.append("<<!>>");
        args.setReplacement(sb.toString());

In the Callback2, i use com.aspose.words.ReplacingArgs.getMatchNode().getText().

 Node matchNode = args.getMatchNode();
            String text = matchNode.getText();
            //切分标记
            if (StringUtils.equals(text, "<<!>>")) {
                tagEnds.add(matchNode);
            } else {
                tagStarts.add(matchNode);
                tagTexts.add(text);
            }

Is it correct?

@Doraemon Here is Java equivalent of the code provided by Ivan:

// Open a real template document here instead.
DocumentBuilder builder = new DocumentBuilder();
Document doc = builder.getDocument();
builder.write("text before <<[tag1]>> text between <<[tag2]>> text after");

// Modify the template to include comments.
//
// 1. Split runs of tag starts and add <<!>> marks for tag ends.
Pattern tagPattern = Pattern.compile("<<.*?>>");
doc.getRange().replace(tagPattern, "", new FindReplaceOptions(new ReplacingCallback1()));

// 2. Split runs of <<!>> marks.
doc.getRange().replace("<<!>>", "<<!>>");

// 3. Collect tag starts, ends, and texts. No replacing occurs here.
ReplacingCallback2 callback2 = new ReplacingCallback2();
doc.getRange().replace(tagPattern, "", new FindReplaceOptions(callback2));

// 4. Insert comments for tags.
for (int i = 0; i < callback2.getTagStarts().size(); i++)
{
    Node tagStart = callback2.getTagStarts().get(i);
    Node tagEnd = callback2.getTagEnds().get(i);
        
    // Note, it is important to trim these characters here, because
    // tags in comments are also replaced with values otherwise.
    String tagText = callback2.getTagTexts().get(i)
            .replace("<", "")
            .replace(">", "");
        
    Comment comment = new Comment(doc);
    comment.getParagraphs().add(new Paragraph(doc));
    comment.getFirstParagraph().getRuns().add(new Run(doc, tagText));
        
    CommentRangeStart commentRangeStart = new CommentRangeStart(doc, comment.getId());
    CommentRangeEnd commentRangeEnd = new CommentRangeEnd(doc, comment.getId());
        
    tagStart.getParentNode().insertBefore(commentRangeStart, tagStart);
    tagEnd.getParentNode().insertBefore(commentRangeEnd, tagEnd);
    tagEnd.getParentNode().insertBefore(comment, tagEnd);
}

// 5. Remove <<!>> marks.
doc.getRange().replace("<<!>>", "");

// Finally, use the modified template to build a report.
ReportingEngine engine = new ReportingEngine();
engine.buildReport(doc, new Object[] { "value1", "value2" }, new String[] { "tag1", "tag2" });
    
doc.save("C:\\Temp\\out.docx");
public static class ReplacingCallback1 implements IReplacingCallback
{
    public int replacing(ReplacingArgs args)
    {
        args.setReplacement(args.getMatch().group() + "<<!>>");
        return ReplaceAction.REPLACE;
    }
}
public static class ReplacingCallback2 implements IReplacingCallback
{
    public int replacing(ReplacingArgs args)
    {
        if (!args.getMatch().group().equals("<<!>>"))
        {
            mTagStarts.add(args.getMatchNode());
            mTagTexts.add(args.getMatch().group());
        }
        else
            mTagEnds.add(args.getMatchNode());
            
        // Simply collect, no need to replace anything.
        return ReplaceAction.SKIP;
    }
        
    public ArrayList<Node> getTagStarts() { return mTagStarts; }
    public ArrayList<Node> getTagEnds() { return mTagEnds; }
    public ArrayList<String> getTagTexts() { return mTagTexts; }
        
    private ArrayList<Node> mTagStarts = new ArrayList<Node>();
    private ArrayList<Node> mTagEnds = new ArrayList<Node>();
    private ArrayList<String> mTagTexts = new ArrayList<String>();
}
1 Like

@alexey.noskov
Thanks for your answer.

It’s work well

1 Like

@alexey.noskov
How can i retain the original tag like “<<[tag1]>>” in the comment rather than “[tag1]”?

@Doraemon This can be doe only by postprocessing the generated report. For example see the following modified code:

// Open a real template document here instead.
DocumentBuilder builder = new DocumentBuilder();
Document doc = builder.getDocument();
builder.write("text before <<[tag1]>> text between <<[tag2]>> text after");

// Modify the template to include comments.
//
// 1. Split runs of tag starts and add <<!>> marks for tag ends.
Pattern tagPattern = Pattern.compile("<<.*?>>");
doc.getRange().replace(tagPattern, "", new FindReplaceOptions(new ReplacingCallback1()));

// 2. Split runs of <<!>> marks.
doc.getRange().replace("<<!>>", "<<!>>");

// 3. Collect tag starts, ends, and texts. No replacing occurs here.
ReplacingCallback2 callback2 = new ReplacingCallback2();
doc.getRange().replace(tagPattern, "", new FindReplaceOptions(callback2));

// 4. Insert comments for tags.
for (int i = 0; i < callback2.getTagStarts().size(); i++)
{
    Node tagStart = callback2.getTagStarts().get(i);
    Node tagEnd = callback2.getTagEnds().get(i);

    // Note, it is important to trim these characters here, because
    // tags in comments are also replaced with values otherwise.
    String tagText = callback2.getTagTexts().get(i)
            .replace("<<", "##")
            .replace(">>", "##");

    Comment comment = new Comment(doc);
    comment.getParagraphs().add(new Paragraph(doc));
    comment.getFirstParagraph().getRuns().add(new Run(doc, tagText));

    CommentRangeStart commentRangeStart = new CommentRangeStart(doc, comment.getId());
    CommentRangeEnd commentRangeEnd = new CommentRangeEnd(doc, comment.getId());

    tagStart.getParentNode().insertBefore(commentRangeStart, tagStart);
    tagEnd.getParentNode().insertBefore(commentRangeEnd, tagEnd);
    tagEnd.getParentNode().insertBefore(comment, tagEnd);
}

// 5. Remove <<!>> marks.
doc.getRange().replace("<<!>>", "");


// Finally, use the modified template to build a report.
ReportingEngine engine = new ReportingEngine();
engine.buildReport(doc, new Object[] { "value1", "value2" }, new String[] { "tag1", "tag2" });

// 6. Restore syntax in comments.
FindReplaceOptions opt = new FindReplaceOptions();
opt.setUseSubstitutions(true);
doc.getRange().replace(Pattern.compile("##([^#]+)##"), "<<$1>>", opt);

doc.save("C:\\Temp\\out.docx");

modification are in the 4th stem and the 6th stem has been added to postprocess the generated report.

@alexey.noskov
That’s awesome! Thank you for your help!

1 Like

@alexey.noskov
I tried to change the texts in word by comments,but when i got the CommentRangeStart and the CommentRangeEnd , i couldn’t go on. I don’t know how to set text between the CommentRangeStart and the CommentRangeEnd.

My code like this.


    public static void loadLinqFromTagComments(Document doc) throws Exception {

        Map<Integer, String> geneCommentTags = new HashMap<>();


        for (Comment comment : getGeneratedTagComments(doc)) {
            int commentId = comment.getId();
            String commentText = comment.getText();
            for (Comment reply : comment.getReplies()) {
                commentText = reply.getText();
            }
            geneCommentTags.put(commentId, commentText);

        }

        List<CommentRange> commentRanges = getCommentRanges(doc);

        for (CommentRange commentRange : commentRanges) {
            String tag = geneCommentTags.get(commentRange.getId());
            if (StringUtils.isBlank(tag)) {
                continue;
            }
            CommentRangeStart start = commentRange.getStart();
            CommentRangeEnd end = commentRange.getEnd();

            //todo what should i do?

        }

    }

    public static List<CommentRange> getCommentRanges(Document doc) {

        MutableMap<Integer, CommentRangeStart> commentRangeStartMap =
                new LazyIterableAdapter<CommentRangeStart>(doc.getChildNodes(NodeType.COMMENT_RANGE_START, true))
                        .toMap(CommentRangeStart::getId, Functions.identity());

        MutableMap<Integer, CommentRangeEnd> commentRangeEndMap =
                new LazyIterableAdapter<CommentRangeEnd>(doc.getChildNodes(NodeType.COMMENT_RANGE_END, true))
                        .toMap(CommentRangeEnd::getId, Functions.identity());

        ArrayList<CommentRange> commentRanges = new ArrayList<>(commentRangeStartMap.size());
        for (Integer id : commentRangeStartMap.keySet()) {
            commentRanges.add(new CommentRange(id, commentRangeStartMap.get(id), commentRangeEndMap.get(id)));
        }

        return commentRanges;

    }

    public static class CommentRange {
        private final int id;
        private final CommentRangeStart start;
        private final CommentRangeEnd end;

        public CommentRange(int commentId, CommentRangeStart start, CommentRangeEnd end) {
            this.id = commentId;
            this.start = start;
            this.end = end;
        }

        public int getId() {
            return id;
        }

        public CommentRangeStart getStart() {
            return start;
        }

        public CommentRangeEnd getEnd() {
            return end;
        }
    }

My test word is
Review.docx (11.0 KB)

I want to change from “value1” to “<<tag2>>”.

@Doraemon I am afraid, there is no easy way to change text between CommentRangeStart and CommentRangeEnd nodes in general, since there might be any kind of nodes. The following article might be useful for you:
https://docs.aspose.com/words/java/extract-selected-content-between-nodes/

But in your particular case, since value is represented as a single Run node, you can simply change text of this Run:

Document doc = new Document("C:\\Temp\\Review.docx");
// Get CommentRangeStart
CommentRangeStart start = (CommentRangeStart)doc.getChild(NodeType.COMMENT_RANGE_START, 0, true);
// Get the next node after CommentRangeStart and change it's text.
if (start.getNextSibling() != null && start.getNextSibling().getNodeType() == NodeType.RUN)
    ((Run)start.getNextSibling()).setText("<<[tag1]>>");
// Save the output.
doc.save("C:\\Temp\\out.docx");

@alexey.noskov
It works fine. Thank you for your support.

1 Like